“Concurrency in Action”原子操作示例的正确性

Posted

技术标签:

【中文标题】“Concurrency in Action”原子操作示例的正确性【英文标题】:Correctness of 'Concurrency in Action' atomic operation example 【发布时间】:2018-12-29 13:13:30 【问题描述】:

我一直在研究“行动中的并发性”立场,但我在理解以下代码示例(清单 5.2)时遇到了问题:

#include <vector>
#include <atomic>
#include <iostream>

std::vector<int> data;
std::atomic<bool> data_ready(false);

void reader_thread()

 while(!data_ready.load())
 
  std::this_thread::sleep(std::milliseconds(1));
 
 std::cout<<”The answer=”<<data[0]<<”\n”;

void writer_thread()

 data.push_back(42); //write of data
 data_ready=true; //write to data_ready flag

本书解释:

(...) 数据的写入发生在写入 data_ready 之前 标志(...)

我担心的是这句话不包括乱序执行。据我了解,当至少两条指令没有依赖的操作数时,可能会发生乱序执行。考虑到这一点:

data_ready=true

不需要任何东西

data.push_back(42)

被执行。因此,不能保证:

数据的写入发生在写入 data_ready 标志之前

我的理解是否正确,或者我不理解乱序执行中的某些内容导致对给定示例的误解?

编辑

感谢您的回答,这很有帮助。我的误解是因为不知道原子类型不仅可以防止部分地转换变量,而且还可以充当内存屏障。

例如,以下代码可能会被编译器或处理器以多种组合重新排序:

d=0;
b=5;
a=10
c=1;

产生以下顺序(多种可能性之一):

b=5;
a=10
c=1;
d=0;

这不是单线程代码的问题,因为没有一个表达式依赖于其他的操作数,但是在多线程应用程序上可能会导致未定义的行为。例如以下代码(初始值:x=0 和 y=0):

Thread 1:       Thread 2:   
x=10;          while(y!=15);
y=15;          assert(x==10);

如果没有编译器重新排序代码或处理器重新排序执行,我们可以说:“由于赋值 y=15 总是发生在赋值 x=10 之后,而断言发生在 while 循环之后,断言永远不会失败”但事实并非如此。实际执行顺序可能如下(多种可能组合之一):

Thread 1:       Thread 2:   
x=10; (4)         while(y!=15); (3)
y=15; (1)         assert(x==10); (2)

默认情况下,原子变量可确保顺序一致。如果上面示例中的 y 是原子的,并且带有 memory_order_seq_cst 默认参数,则以下句子为真: - 之前在线程 1 (x=10) 中发生的事情在线程 2 中也像之前一样可见。 - 在线程 2 中 while(y!=15) 之后发生的事情在线程 1 中也可见 因此它的断言永远不会失败。

一些可能有助于理解的来源:

Memory model synchronization modes - GCC CppCon 2015: Michael Wong “C++11/14/17 atomics and memory model..." Memory barriers in C

【问题讨论】:

what and how does happens-before happen in c++0x的可能重复 【参考方案1】:

我理解您的担忧,但是书中的代码很好。默认情况下,使用原子的每个操作都是memory_order_seq_cst,这意味着在一个线程中写入之前发生的所有事情都会在其余线程中读取之前发生。你可以用这个std::memory_order 想象原子操作是这样的:

std::atomic<bool> a;
//equivalent of a = true
a.assign_and_make_changes_from_thread_visible(true);

//equvalent of a.load()
a.get_value_and_changes_from_threads();

【讨论】:

【参考方案2】:

来自 Effective Modern C++,Item 40,它说“std::atomics 对如何重新排序代码施加了限制,其中一个限制是在源代码中没有任何代码在写入 std::atomic 之前变量可能会在之后发生。”请注意,当使用顺序一致性时这是正确的,这是一个公平的假设。

【讨论】:

以上是关于“Concurrency in Action”原子操作示例的正确性的主要内容,如果未能解决你的问题,请参考以下文章

Manning新书C++并行实战,592页pdf,C++ Concurrency in Action

《C++ Concurrency in Action》读书笔记三 同步并发操作

Cpp Concurrency In Action(读书笔记3)——同步并发操作

C++并发编程----异常安全的并行算法(《C++ Concurrency in Action》 读书笔记)

C++并发编程----异常安全的并行算法(《C++ Concurrency in Action》 读书笔记)

C++并发编程----无锁实现线程安全队列(《C++ Concurrency in Action》 读书笔记)