2017-06-13 82 views
2

我有以下类:C++ 11 const - 我的代码是否线程安全?

class Object 
{ 
public: 
    Object() {} 

    const std::string& get_name() const 
    { 
    if(_name.empty()) { 
     std::lock_guard<std::mutex> lock(_lock); 

     // Check if its still empty. Some other thread might have gotten here first 
     if(_name.empty()) { 
     //Run expensive operation 
     _name = get_object_name(); 
     } 
    } 

    return _name; 
    } 

private: 
    std::string get_object_name(); // <- Expensive function 

    mutable std::mutex _lock; 
    mutable std::string _name; 
}; 

由于对get_object_name是一个昂贵的功能的事实,我愿做一个排序延迟初始化的并且只调用它的第一次get_name()被调用。如果它从未被调用,那么我不会浪费资源来获取名称。

我很担心第一次致电_name.empty()。我的当前代码是否保证是线程安全的,还是需要将锁移动到函数的顶部?

我看了一些香草萨特的会谈,特别是this one,在这张幻灯片上出现:

http://i.imgur.com/Jz4luYe.png

导致我相信,到empty()调用是线程安全的。但我的变量(_name)是mutable。这个“const ==线程安全”规则是否仍然适用于此?

get_name()是唯一可以修改_name的功能。

+2

有该模式的名称:双检锁(https://en.wikipedia.org/维基/双checked_locking)。它适用于某些环境,而不适用于其他环境。当它不起作用时,原因很微妙。 –

回答

3

不,它不是线程安全的,因为您在mutex之外访问_name,这会中断同步。

一个可能的解决方案是使用标准库提供的std::call_once机制。

class Object 
{ 
public: 
    Object() {} 

    const std::string& get_name() const 
    { 
    std::call_once(flag, [&] { _name = get_object_name(); }); 

    return _name; 
    } 

private: 
    std::string get_object_name() const; // <- Expensive function 

    mutable std::string _name; 
    mutable std::once_flag flag; 
}; 

这保证不会多次调用get_object_name()。第一次调用将初始化string,并发呼叫将阻塞,直到lambda完成。
同步是完全照顾,这意味着任何线程得到参考string可以安全地从它读取。

+0

是的,这就是我所害怕的。你的'call_once'解决方案非常酷,不过我喜欢它。我认为它有一个小的开销(没有什么能够让我的程序减慢一些数量级,如果我把它扔到循环中),对吗? – Mark

+0

@Mark“call_once”的可能实现是_double-checked锁定模式_。开销(初始化后)可能相当于原子加载/获取,这相当于'X86' – LWimsey

+0

上的正常加载,谢谢。 – Mark

0

启动C++ 11 static变量以线程安全方式进行初始化。 如果得到这个名字的昂贵操作变得static我认为以下比较好:

class Object 
{ 
public: 
    const std::string& get_name() const 
    { 
     static std::string name = expensive_get_name(); 
     return name; 
    } 
private: 
    static std::string expensive_get_name() 
    { 
     return "Name"; 
    } 
}; 
+0

这会导致所有对象具有_same_名称,这与名称为“const”(即非静态)方法的事实没有关联。 – MSalters

+0

是的,因为@MSalters表示会导致所有对象实例具有相同的名称。对'get_name()'的第一次调用会初始化该静态变量,并将用于所有其他对该函数的调用,即使对于其他对象也是如此。 – Mark