我有以下类:
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(的一些演讲,特别是这张幻灯片:
http://i.imgur.com/Jz4luYe.png
这让我相信调用empty()
是线程安全的。但是我的变量(_name
(是mutable
。"const == 线程安全"规则在这里仍然适用吗?
get_name()
是唯一可以修改_name
的函数,就此而言。
不,它不是线程安全的,因为您(读取(访问_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
引用的线程都可以安全地从中读取。
从 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";
}
};