使用互斥元进行线程安全的延迟初始化

像下面这段代码一样的朴素的转换,会引起使用该资源的线程产生不必要的序列化。这是因为每个线程都必须等待互斥元,以检查资源是否已经被初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
std::shared_ptr<Resource> resource_ptr;
std::mutex m;

void foo()
{
std::unique_lock<std::mutex> lk(m);
if (!source_ptr)
{
resource_ptr.reset(new Resource); // 多线程唯一需要保护的部分
}
lk.unlock();
resource_ptr->do_something();
}

语气锁定互斥元并且显式地检查指针,还不如每个线程都可以用 std::call_once,到 call_once 返回时,指针将会被某个线程初始化(以完全同步的方式),这样就安全了。使用 std::call_once 比显式地使用互斥元通常会有更低的开销,特别是初始化已经完成的时候。

1
2
3
4
5
6
7
8
9
10
11
12
13
std::sharef_ptr<Resource> resource_ptr;
std::once_flag resource_flag;

void init_resource()
{
resource_ptr.reset(new Resource);
}

void foo()
{
std::call_once(resource_flag, init_resource);
resource_ptr->do_something();
}

std::call_once() 可以容易地用于类成员的延迟出初始化:

1
2
3
4
5
void X::call()
{
std::call_once(init_flag, &X:init, this);
do_something();
}