【发布时间】:2016-11-28 16:07:03
【问题描述】:
我不明白,如果我们在下面的示例中有 2 个线程,为什么没有 release sequence 会出现问题。我们对原子变量 count 只有 2 个操作。 count 依次递减,如输出所示。
来自 Antony Williams 的 C++ Concurrency in Action:
我提到你可以在
store到一个原子变量和来自另一个线程的那个原子变量的load之间得到一个synchronizes-with relationship,即使在store之间有一系列read-modify-write操作。和load,前提是所有操作都经过适当标记。如果存储标记为memory_order_release、memory_order_acq_rel或memory_order_seq_cst,并且加载标记为memory_order_consume、memory_order_acquire或memory_order_acquire或memory_order_seq_cst,则链中的每个操作都加载由之前的操作,那么操作链构成一个发布序列和初始存储synchronizes-with(对于memory_order_acquire或memory_order_seq_cst)或者是dependency-ordered-before(对于memory_order_consume) 最终加载。链中的任何原子读取-修改-写入操作都可以具有任何内存顺序(甚至是memory_order_relaxed)。要了解这意味着什么(发布顺序)以及它为何如此重要,请考虑将
atomic<int>用作共享队列中项目数的计数,如下面的清单所示。处理事情的一种方法是让生成数据的线程将项目存储在共享缓冲区中,然后执行
count.store(number_of_items, memory_order_release)#1 让其他线程知道数据可用。然后,在实际读取共享缓冲区 #4 之前,使用队列项目的线程可能会执行count.fetch_sub(1,memory_ order_acquire)#2 从队列中声明项目。一旦计数变为零,就没有更多的项目了,线程必须等待#3。
#include <atomic>
#include <thread>
#include <vector>
#include <iostream>
#include <mutex>
std::vector<int> queue_data;
std::atomic<int> count;
std::mutex m;
void process(int i)
{
std::lock_guard<std::mutex> lock(m);
std::cout << "id " << std::this_thread::get_id() << ": " << i << std::endl;
}
void populate_queue()
{
unsigned const number_of_items = 20;
queue_data.clear();
for (unsigned i = 0;i<number_of_items;++i)
{
queue_data.push_back(i);
}
count.store(number_of_items, std::memory_order_release); //#1 The initial store
}
void consume_queue_items()
{
while (true)
{
int item_index;
if ((item_index = count.fetch_sub(1, std::memory_order_acquire)) <= 0) //#2 An RMW operation
{
std::this_thread::sleep_for(std::chrono::milliseconds(500)); //#3
continue;
}
process(queue_data[item_index - 1]); //#4 Reading queue_data is safe
}
}
int main()
{
std::thread a(populate_queue);
std::thread b(consume_queue_items);
std::thread c(consume_queue_items);
a.join();
b.join();
c.join();
}
输出(VS2015):
id 6836: 19
id 6836: 18
id 6836: 17
id 6836: 16
id 6836: 14
id 6836: 13
id 6836: 12
id 6836: 11
id 6836: 10
id 6836: 9
id 6836: 8
id 13740: 15
id 13740: 6
id 13740: 5
id 13740: 4
id 13740: 3
id 13740: 2
id 13740: 1
id 13740: 0
id 6836: 7
如果有一个消费者线程,这很好;
fetch_sub()是读取,具有memory_order_acquire语义,而存储具有memory_order_release语义,因此存储与加载同步,线程可以从缓冲区中读取项目。如果有两个线程读取,第二个
fetch_sub()将看到第一个写入的值,而不是存储写入的值。如果没有关于release sequence的规则,第二个线程不会有happens-before relationship和第一个线程,并且读取共享缓冲区是不安全的,除非第一个fetch_sub()也有memory_order_release语义,这将在两个消费者线程之间引入不必要的同步。如果没有release sequence规则或fetch_sub操作上的memory_order_release,则无需要求queue_data的存储对第二个消费者可见,并且您将发生数据竞争。
他是什么意思?两个线程都应该看到count 的值是20?但在我的输出中,count 在线程中依次递减。
谢天谢地,第一个
fetch_sub()确实参与了发布序列,因此store()与第二个fetch_sub()同步。两个消费者线程之间仍然没有同步关系。如图 5.7 所示。图5.7中虚线表示释放顺序,实线表示happens-before relationships
【问题讨论】:
-
真正的问题是什么?为什么 std 不只是说 acq 读取与曾经发生的所有 rel 存储同步?
标签: c++ multithreading c++11 memory-model stdatomic