我有以下课程:
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()
。我当前的代码是保证是线程安全的还是我需要将锁移动到函数的顶部?
我看了一些Herb Sutter的谈话,特别是this one,这张幻灯片出现了:
http://i.imgur.com/Jz4luYe.png
让我相信对empty()
的调用是线程安全的。但我的变量(_name
)是mutable
。这个“const ==线程安全”规则是否仍适用于此?
get_name()
是唯一可以修改_name
的函数。
答案 0 :(得分:3)
不,它不是线程安全的,因为您(读取)访问_name
之外的mutex
,这会破坏同步。
可能的解决方案是使用标准库提供的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
引用的线程都可以安全地从中读取。
答案 1 :(得分: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";
}
};