在没有互斥体的 C++11 中实现共享整数计数器的最简单方法:
Posted
技术标签:
【中文标题】在没有互斥体的 C++11 中实现共享整数计数器的最简单方法:【英文标题】:Easiest way to implement shared integer counter in C++11 without mutexes: 【发布时间】:2014-03-17 02:30:48 【问题描述】:假设我们有以下代码来计算某事发生的次数:
int i=0;
void f()
// do stuff . . .
if(something_happens) ++i;
int main()
std::vector<std::thread> threads;
for(int j = 0; j< std::thread::hardware_concurrency(); ++j)
threads.push_back(std::thread(f));
std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread_join));
std::cout << "i = " << i << '\n';
就目前而言,i 上有一个明确的竞争条件。使用 C++11,(1) 消除这种竞争条件的最简单方法是什么,以及 (2) 最快的方法是什么?最好不使用互斥锁。谢谢。
更新:使用注释来使用原子,我得到了一个在英特尔编译器版本 13 下编译的工作程序:
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <algorithm>
std::atomic<unsigned long long> i = 0;
void f(int j)
if(j%2==0)
++i;
int main()
std::cout << "Atomic i = " << i << "\n";
int numThreads = 8; //std::thread::hardware_concurrency() not yet implemented by Intel
std::vector<std::thread> threads;
for(int k=0; k< numThreads; ++k)
threads.push_back(std::thread(f, k));
std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));
std::cout << "Atomic i = " << i << "\n";
【问题讨论】:
原子。如果你使用的是GCC,搜索__sync builtins
,否则我不知道。
您需要一个有意义的正在进行的计数,还是只需要所有线程结束后的最终计数?
【参考方案1】:
您可能想查看atomic types。您无需锁定/互斥即可访问它们。
【讨论】:
+1 用于提及 C++11<atomic>
,错过了问题标记为 C++11 :D【参考方案2】:
我们通过声明一个数组 [nThreads] 解决了一个类似的问题,然后我们给每个线程一个从 0 到 n 的 id,然后线程可以安全地写入数组中的位置。然后你可以对数组求和以获得总和。但是,这仅在您不需要在所有线程都死之前对数组求和时才有用。
为了更高效,我们在每个线程上都有一个本地计数器,然后在线程死亡之前将其附加到数组中。
示例(伪代码:)
counter[nThreads];
thread(int id)
// Do stuff
if(something happened)
counter[id]++;
或
counter[nThreads];
thread(int id)
int localcounter = 0;
//Do stuff
if(something happened)
localcounter++;
//Thread is about to die
counter[id] = localcounter;
【讨论】:
+1,不错的概念:这对于更复杂的数据可能很方便。这就像一个简单的 map-and-reduce。 @DarkDust 打败我说这只是 map-reduce 你掉进了经典陷阱:你没有在每个变量之间填充数组,至少相当于缓存行(128 字节)。正因为如此,你没有注意到它,但硬件访问是由 MESI 协议互锁的。 @v.oddou 这就是我们采用第二种方法的原因。但这真是个好主意!【参考方案3】:您可以使用InterlockedIncrement 函数。
MSDN 上以 Synchronization Functions 的名义记录了许多以原子方式改变变量的函数 - 它们可能对您有用。
【讨论】:
需要它同时在 Linux 和 Windows 上工作。但如果需要,我会使用该代码。 Boost:: atomic 是跨平台的!【参考方案4】:这不仅仅是一个竞争条件,如果你的编译器决定这样做,你可能根本无法在线程之间传递实际的 i
值。
显然atomic
是个好方法。互斥锁也是一个好方法,当你没有碰撞时,它们和原子一样快。只有当他们真正需要让内核摆弄sleeping
和ready
线程队列时,它们才会变慢。如果等待信号不使用condition variable
,可能会变得很麻烦,在这种情况下,您可能必须等待调度内核滴答声才能让您的ready
线程成为running
,这可能很长(30 毫秒) .
不过,原子可以让您达到最佳状态,甚至可能比 condition variable
s 更容易维护,因为不必关心 spurious
事件和 notify_one
与 notify_all
等。
如果您检查 C++11 制造的 STL 的 shared_ptr 基类,它们包含的 base_count
或(base_shared_count 或其他)完全符合您的需要。
如果愿意,您还可以检查新的 boost::shared_count 实现。
【讨论】:
以上是关于在没有互斥体的 C++11 中实现共享整数计数器的最简单方法:的主要内容,如果未能解决你的问题,请参考以下文章