一、先来看一些例子
- 有些代码中,某些代码会被多线程使用,但是当一个线程使用之后,其他线程就不能再去使用了
- 例如:
- 下面的代码会调用一个initialize()初始化函数对某些东西进行初始化,当一个线程去初始化之后,另外的线程再次执行时就不需要再次去初始化了
- 但是在多线程环境下,下面的if会造成data race,因为多个线程可能同时执行到了if,并且没有任何措施,导致都执行了if,因此造成两次初始化,与我们的初衷不符合
-
void initialize();
-
-
bool initialized =
false;
-
//多线程环境操作下,此处会发生data race
-
if (!initialized)
-
{
-
initialize();
-
initialized =
true;
-
}
- 例如:
- 下面的代码与上面的演示案例类似。在多线程环境下时,可能有多个线程执行到foo()函数中的if,造成data race
-
static
std::
vector<
std::
string> staticData;
-
std::
vector<
std::
string> initializeStaticData();
-
-
void foo()
-
{
-
//多线程环境操作下,此处会发生data race
-
if (staticData.empty())
-
{
-
staticData = initializeStaticData();
-
}
-
}
- 这些案例在单线程中不会发生错误,但是在多线程环境中,如果多个线程对数据进行访问,那么就会发生data race
二、std::once_flag、std::call_once
- 对于上面的data race,有两种解决方案:
- 一种使用mutex来对共享数据进行保护操作,确保在同一时间下,只有一个线程可以对共享数据尽心操作
- C++标准库还提供了一个特殊的解法,那就是使用std::once_flag和std::call_once(头文件为<mutex>)
- 例如在文章最开始的两个data race例子,我们可以将代码修改为下:
-
void initialize();
-
-
void foo()
-
{
-
std::once_flag oc;
-
//...
-
-
//执行initialize函数,当下次再次执行到这个语句时,就不会再去执行了,因为oc被执行过一次了
-
std::call_once(oc, initialize);
-
}
-
static
std::
vector<
std::
string> staticData;
-
std::
vector<
std::
string> initializeStaticData();
-
-
void foo()
-
{
-
static
std::once_flag oc;
-
//执行initializeStaticData函数,当下次再次执行到这个语句时,就不会再去执行了,因为oc被执行过一次了
-
std::call_once(oc, [] {staticData = initializeStaticData(); });
-
}
- std::call_once的参数:
- 参数1为一个std::once_flag变量
- 参数2为一个可调用对象,如function、member function或lambda
- 后面的参数是可选的,作为可调用对象的参数传递
工作原理
- 对于同一段代码,当call_once()被调用一次之后,当再次执行到这段代码时,once_flag标志被执行过一次之后,就不会再次执行了,因此达到了call_once中的可调用对象只能执行一次的目的
三、典型使用案例
- 典型的例子就是lazy initialization(缓式初始化):第一次某个线程需要某数据而该数据必须备妥,于是此时处理它(但不在先前处理,因为你想节省空间;如果非必要的话就不处理它)
- 因此,多线程环境下的缓式初始化应该如下所示:
-
class X
-
{
-
public:
-
data getData()const
-
{
-
std::call_once(initDataFlag, &X::initData,
this);
-
//...
-
}
-
private:
-
mutable
std::once_flag initDataFlag;
-
void initData()const;
-
};
四、相关注意事项
- 原则上你可以使用同一个once_flag调用不同的函数。之所以把once_flag当做第一实参传递给call_once()就是为了确保传入的机能只能被执行一次。因此,如果第一次调用成功,下一次调用又带着相同的once_flag,传入的技能就不会被调用——即使该机能与第一次有异
- 被调用的函数所造成的任何异常会被call_once()抛出。此情况第一次调用被视为不成功,因此下一次call_once()还可以再调用它所接受的技能
转载:https://blog.csdn.net/qq_41453285/article/details/105604024
查看评论