关于线程安全的推理可能很困难,而且我不是 C++11 内存模型的专家。但是,幸运的是,您的示例非常简单。我重写了这个例子,因为构造函数是无关紧要的。
简化示例
问题:下面的代码正确吗?或者执行会导致未定义的行为?
// Legal transfer of pointer to int without data race.
// The receive function blocks until send is called.
void send(int*);
int* receive();
// --- thread A ---
/* A1 */ int* pointer = receive();
/* A2 */ int answer = *pointer;
// --- thread B ---
int answer;
/* B1 */ answer = 42;
/* B2 */ send(&answer);
// wait forever
答:的内存位置可能存在数据竞争answer
,因此执行会导致未定义的行为。详情见下文。
数据传输的实现
当然,答案取决于函数send
和receive
. 我使用以下无数据竞争的实现。请注意,只使用了一个原子变量,所有内存操作都使用std::memory_order_relaxed
. 基本上这意味着,这些函数不限制内存重新排序。
std::atomic<int*> transfer{nullptr};
void send(int* pointer) {
transfer.store(pointer, std::memory_order_relaxed);
}
int* receive() {
while (transfer.load(std::memory_order_relaxed) == nullptr) { }
return transfer.load(std::memory_order_relaxed);
}
内存操作顺序
在多核系统上,一个线程可以看到与其他线程看到的不同顺序的内存变化。此外,编译器和 CPU 都可以在单个线程中重新排序内存操作以提高效率——而且它们一直都在这样做。原子操作std::memory_order_relaxed
不参与任何同步,也不强加任何顺序。
在上面的例子中,允许编译器重新排序线程 B 的操作,并在 B1 之前执行 B2,因为重新排序对线程本身没有影响。
// --- valid execution of operations in thread B ---
int answer;
/* B2 */ send(&answer);
/* B1 */ answer = 42;
// wait forever
数据竞赛
C++11 对数据竞争的定义如下(N3290 C++11 草案):“如果程序的执行包含不同线程中的两个冲突操作,则程序的执行包含数据竞争,其中至少一个不是原子的,并且两者都不是发生在另一个之前。任何此类数据竞争都会导致未定义的行为。” 并且该术语发生在之前在同一文档中的较早定义。
在上面的例子中,B1 和 A2 是相互冲突的非原子操作,两者都不先发生。这很明显,因为我在上一节中已经表明,两者可以同时发生。
这是 C++11 中唯一重要的事情。相比之下,Java 内存模型还尝试定义存在数据竞争时的行为,并且他们花了将近十年的时间才提出合理的规范。C++11 没有犯同样的错误。
更多的信息
我有点惊讶这些基础知识并不为人所知。确定的信息来源是C++11 标准中的多线程执行和数据竞争部分。但是,规范很难理解。
Hans Boehm 的演讲是一个很好的起点——例如在线视频:
还有很多其他好的资源,我在其他地方提到过,例如: