C++ C++;:释放构造函数中所需的屏障,该构造函数创建访问构造对象的线程

C++ C++;:释放构造函数中所需的屏障,该构造函数创建访问构造对象的线程,c++,multithreading,c++11,memory-barriers,C++,Multithreading,C++11,Memory Barriers,如果我在构造函数中创建一个线程,并且该线程访问该对象,那么在线程访问该对象之前,我是否需要引入一个释放屏障?具体来说,如果我有下面的代码(),我是否需要在构造函数(注释掉的行)中锁定互斥锁?我需要确保worker\u线程看到写入run\u worker\u线程的操作,因此不会立即退出。我意识到在这里使用原子布尔更好,但我有兴趣理解这里的内存排序含义。根据我的理解,我认为我确实需要在构造函数中锁定互斥体,以确保构造函数中互斥体的解锁提供的释放操作与通过调用shouldRun()在threadLoo

如果我在构造函数中创建一个线程,并且该线程访问该对象,那么在线程访问该对象之前,我是否需要引入一个释放屏障?具体来说,如果我有下面的代码(),我是否需要在构造函数(注释掉的行)中锁定互斥锁?我需要确保
worker\u线程
看到写入
run\u worker\u线程
的操作,因此不会立即退出。我意识到在这里使用原子布尔更好,但我有兴趣理解这里的内存排序含义。根据我的理解,我认为我确实需要在构造函数中锁定互斥体,以确保构造函数中互斥体的解锁提供的释放操作与通过调用
shouldRun()
threadLoop()中锁定互斥体提供的获取操作同步

类线程活套{
公众:
ThreadLooper(std::string thread\u name)
:thread_name_{std::move(thread_name)},loop_counter_{0}{
//std::锁\保护锁(互斥锁);
run\u worker\u thread\u=true;
辅助线程=std::thread([this](){threadLoop();});
//互斥解锁提供释放语义
}
~ThreadLooper(){
{
std::锁\保护锁(互斥锁);
run\u worker\u thread\u=false;
}
if(辅助线程可接合()){
辅助线程连接();
}

cout您不需要任何障碍,因为
线程
构造函数与传递给它的函数调用同步。 在标准语中:

构造函数调用的完成与f副本调用的开始同步


有点正式的证据:
run\u worker\u thread=true;
A)根据完整表达式在
线程创建(B)对象之前排序。
线程构建与闭包对象执行同步(C)根据上述规则。因此,AC

A seq在B之前,B与C同步,A发生在C之前->这是标准术语中的正式证明

<> P>在C++ 11 +时代分析程序时,你应该坚持C++的内存和执行模型,忘记编译器可能会做的或可能不做的障碍和排序,这些只是实现细节。唯一重要的是C++中的形式证明。编译器必须服从和执行(而不做)。尽可能地遵守规则

但是为了完整起见,让我们用编译器的眼光来看待代码,并试图理解为什么在这种情况下它不能对任何东西进行重新排序。我们都知道“好像”规则,在该规则下,如果您不能告诉某些指令已被重新排序,编译器可能会对它们进行重新排序。因此,如果我们有一些
bool
标志设置:

flag1 = true; // A
flag2 = false;// B
允许按如下方式执行这些行:

flag2 = false;// B
flag1 = true;// A
尽管事实上A在B之前排序。它可以做到这一点,因为我们无法区分差异,我们无法通过观察程序行为来捕捉它重新排序我们的指令,因为除了“sequenced before”之外,这些行之间没有关系。但让我们回到我们的案例:

run_worker_thread_ = true; // A
worker_thread_ = std::thread(...); // B
这种情况看起来可能与上面的
bool
变量相同。如果我们不知道
线程
对象(除了在A表达式之后排序)与某个对象同步(为简单起见,让我们忽略此项)但是我们发现,如果某个事物是在另一个事物之前排序的,而该事物又与另一个事物同步,那么它就发生在该事物之前。因此标准要求A表达式发生在我们的B表达式与之同步的事物之前

这一事实禁止编译器对A&B表达式进行重新排序,因为如果它这样做了,我们会突然分辨出它们之间的区别。因为如果它这样做了,那么C表达式(某种东西)可能看不到A提供的可见副作用。因此,仅通过观察程序执行,我们可能会捕获作弊编译器!因此,它必须使用一些屏障。无论是编译器屏障还是硬件屏障,它都必须存在,以确保这些指令不会重新排序。因此,您可以假设它在构建完成时使用一个释放围栏,在关闭对象执行时使用一个获取围栏,这大致描述了引擎盖下发生的事情


看起来你也把互斥当作一种神奇的东西,它总是有效的,不需要任何证明。因此,出于某种原因,你相信互斥,而不相信线程。但问题是它没有魔力,它唯一的保证就是与先前的
解锁同步,反之亦然。所以它提供了与
线程
相同的保证。

您的
互斥体
似乎只保护
bool run\u worker\u thread
,因此您可以使用
std::atomic
并删除
互斥体
@Jarod42对,我在帖子中提到过。我不确定我是否遵守,因为对我来说,这似乎不够。似乎就像我真的需要分配
run\u worker\u thread\uu
与“调用f副本的开始”同步一样.@MikeSweeney您确实需要这种同步,而且它是隐式的。
线程
的构造意味着一个内存围栏。@MikeSweeney这确实足够了。添加了更多信息。@ixSci我不太理解求值顺序链接。如果
线程
对象创建是另一个基本变量赋值,则
运行\u worker\u thread=true;
仍按顺序排列
run_worker_thread_ = true; // A
worker_thread_ = std::thread(...); // B