对于等待工作线程的主机线程,我应该使用哪种内存顺序?

Posted

技术标签:

【中文标题】对于等待工作线程的主机线程,我应该使用哪种内存顺序?【英文标题】:Which Memory Order Should I use for a Host Thread waiting on Worker Threads? 【发布时间】:2016-06-10 19:20:22 【问题描述】:

我有代码可以将任务分派到一个 asio io_service 对象以进行远程处理。据我所知,代码运行正常,但不幸的是,我对内存排序知之甚少,而且我不确定在检查原子标志以确保最佳性能时应该使用哪些内存顺序。

//boost::asio::io_service;
//^^ Declared outside this scope
std::vector<std::atomic_bool> flags(num_of_threads, false);
//std::vector<std::thread> threads(num_of_threads);
//^^ Declared outside this scope, all of them simply call the run() method on io_service

for(int i = 0; i < num_of_threads; i++) 
    io_service.post([&, i]
        /*...*/
        flags[i].store(true, /*[[[1]]]*/);
    );


for(std::atomic_bool & atm_bool : flags) while(!atm_bool.load(/*[[[2]]]*/)) std::this_thread::yield();

所以基本上,我想知道的是,我应该用什么来代替[[[1]]][[[2]]]

如果有帮助,代码在功能上类似于以下内容:

std::vector<std::thread> threads;
for(int i = 0; i < num_of_threads; i++) threads.emplace_back([]/*...*/);
for(std::thread & thread : threads) thread.join();

除了我的代码使线程在外部线程池中保持活动状态并向它们分派任务。

【问题讨论】:

如有疑问,只需保留完整的内存屏障(默认)。 【参考方案1】:

你想在设置标志的线程和看到它被设置的线程之间建立 happens-before 关系。这意味着一旦线程看到标志被设置,它也会看到其他线程在设置它之前所做的一切的效果(否则不能保证)。

这可以使用发布-获取语义来完成:

flags[i].store(true, std::memory_order_release);
// ...
while (!atm_bool.load(std::memory_order_acquire)) ...

请注意,在这种情况下,使用阻塞的操作系统级信号量可能比在标志数组上旋转等待更干净。如果做不到这一点,使用已完成任务的计数而不是为每个任务检查一组标志仍然会更有效。

【讨论】:

仅供参考,load-acquire 和 store-release 在 x86 上是免费的(加载和存储都具有内置的获取/释放语义),但您仍然需要将它们写出来,因为允许编译器重新排序否则(它也是可移植的和正确的)。

以上是关于对于等待工作线程的主机线程,我应该使用哪种内存顺序?的主要内容,如果未能解决你的问题,请参考以下文章

让线程等待值在内存中更改的有效方法?

对于缓存的 GPU,哪种内存访问模式更有效?

iOS下的并行开发

无需等待的并发代码

C++ 原子内存顺序与诸如 notify() 之类的线程事件

iOS开发系列--并行开发其实很容易