使用 std::atomic<> 进行多线程

Posted

技术标签:

【中文标题】使用 std::atomic<> 进行多线程【英文标题】:multithreading with std::atomic<> 【发布时间】:2019-07-03 11:05:49 【问题描述】:

在下面的小代码示例中,我不明白,当一个线程开始增加计数器时,该线程会将计数器地址写入内核之间共享的缓存中,并使计数器锁定在缓存中,直到该线程已经完成了计数器的写入,但是如果另一个尝试在前一个线程读写修改之间给这个计数器添加+1,他会看到缓存中的数据被锁定,然后呢?第二个线程会休眠还是等到缓存中的计数器解锁..?

如果线程休眠,我看不到原子和互斥锁对于小尺寸数据的好处:

// this_thread::yield example
#include <iostream>       // std::cout
#include <thread>         // std::thread, std::this_thread::yield
#include <atomic>         // std::atomic

std::atomic<bool> ready (false);
std::atomic<int> counter (0);

void count1m(int id) 
  while (!ready)              // wait until main() sets ready...
    std::this_thread::yield();
  
  for (volatile int i=0; i<10000; ++i) 
        counter +=1;


int main ()

  std::thread threads[10];
  std::cout << "race of 10 threads that count to 1 million:\n";
  for (int i=0; i<10; ++i) threads[i]=std::thread(count1m,i);
  ready = true;               // go!
  for (auto& th : threads) th.join();
  std::cout << counter;

【问题讨论】:

很确定你不需要volatile int i=0int i = 0 就足够了。 它不是真正的“锁”,核心只是同步缓存行(如果使用缓存)。是的,只要是缓存未命中,核心就会等待缓存更新。用于同步缓存/内存的策略取决于 cpu 架构,在这里写下来有点宽泛。 但是当没有“锁”的线程想要更​​新计数器时会发生什么?他是在睡觉还是别的什么? @BenzaitSofiane 它停止了。从字面上看,只是停止内核,直到可以检索到缓存行。 我想我忘记了什么,因为我真的不明白这是什么意思:线程停止核心? 【参考方案1】:

“缓存锁”只是延迟对 MESI 无效或共享请求的响应的核心。

它不是互斥体或任何东西,它是单个内核可以通过调整它使用现有 MESI 缓存一致性协议的方式来完成的事情,该协议确保缓存永远不会在同一地址出现冲突的值。其他核心不必知道“锁”,他们只是看到对共享线路请求的响应延迟。 (无论如何,它没有任何硬性截止日期或预期的响应时间;它取决于争用以及有多少其他内核也在等待对线路的独占访问以提交 他们的 存储或 RMW。)

请参阅Can num++ be atomic for 'int num'? 了解完整详情。 (可以说不是重复,因为它从更广泛的前提开始,涵盖了与此处无关的许多内容。)

C++ 内存模型假设一致的共享内存,而这基本上是所有多核机器所拥有的。始终 (AFAIK) 使用 MESI 缓存一致性协议的某些变体。


第二个线程会休眠吗?

不,这都是硬件级别的。操作系统不知道内核在等待缓存行时停止。

这与常规缓存未命中非常相似,等待数据从 DRAM 而不是另一个内核到达。

【讨论】:

以上是关于使用 std::atomic<> 进行多线程的主要内容,如果未能解决你的问题,请参考以下文章

如何在 std::atomic<T> 上实现一个简单的自旋锁,以便编译器不会对其进行优化?

为啥不能交换 std::atomic<T> ?

std::atomic<std::string> 是不是正常工作?

原子读取然后用 std::atomic 写入

std::atomic_flag 停止多个线程

在 Windows 中等待 std::atomic<int> 的正确方法?