如何使用std :: atomic实现可重用的线程屏障 [英] How to implement a re-usable thread barrier with std::atomic
问题描述
我有N个线程执行各种任务,这些线程必须定期与线程屏障同步,如下图所示3线程和8个任务。 ||
I have N threads performing various task and these threads must be regularly synchronized with a thread barrier as illustrated below with 3 thread and 8 tasks. The || indicates the temporal barrier, all threads have to wait until the completion of 8 tasks before starting again.
Thread#1 |----task1--|---task6---|---wait-----||-taskB--| ...
Thread#2 |--task2--|---task5--|-------taskE---||----taskA--| ...
Thread#3 |-task3-|---task4--|-taskG--|--wait--||-taskC-|---taskD ...
我找不到一个可行的解决方案,想到了一本小册子Semaphores http://greenteapress.com/semaphores/index.html 是鼓舞人心的。我想出了一个解决方案使用std ::原子如下所示似乎使用三个std ::原子。
我担心我的代码分解在角落案例,因此引用的动词。所以,你可以共享关于验证这样的代码的建议吗?你有一个更简单的傻瓜验证码可用吗?
I couldn’t find a workable solution, thought the little book of Semaphores http://greenteapress.com/semaphores/index.html was inspiring. I came up with a solution using std::atomic shown below which "seems" to be working using three std::atomic. I am worried about my code breaking down on corner cases hence the quoted verb. So can you share advise on verification of such code? Do you have a simpler fool proof code available?
std::atomic<int> barrier1(0);
std::atomic<int> barrier2(0);
std::atomic<int> barrier3(0);
void my_thread()
{
while(1) {
// pop task from queue
...
// and execute task
switch(task.id()) {
case TaskID::Barrier:
barrier2.store(0);
barrier1++;
while (barrier1.load() != NUM_THREAD) {
std::this_thread::yield();
}
barrier3.store(0);
barrier2++;
while (barrier2.load() != NUM_THREAD) {
std::this_thread::yield();
}
barrier1.store(0);
barrier3++;
while (barrier3.load() != NUM_THREAD) {
std::this_thread::yield();
}
break;
case TaskID::Task1:
...
}
}
}
推荐答案
对于原子变量,使用它们中的三个作为一个障碍只是过度杀死,只会使问题复杂化。你知道线程的数量,所以你可以简单地自动增加一个计数器每次线程进入屏障,然后旋转,直到计数器变得大于或等于N.这样的东西:
With atomic variables, using three of them for a barrier is simply overkill that only serves to complicate the issue. You know the number of threads, so you can simply atomically increment a single counter every time a thread enters the barrier, and then spin until the counter becomes greater or equal to N. Something like this:
void barrier(int N) {
static std::atomic<unsigned int> gCounter = 0;
gCounter++;
while((int)(gCounter - N) < 0) std::this_thread::yield();
}
如果你没有比CPU核心更多的线程,时间,您可能想要删除对 std :: this_thread :: yield()
的调用。这个电话可能是真的很贵(超过一个微秒,我下注,但我没有测量)。
If you don't have more threads than CPU cores and a short expected waiting time, you might want to remove the call to std::this_thread::yield()
. This call is likely to be really expensive (more than a microsecond, I'd wager, but I haven't measured it). Depending on the size of your tasks, this may be significant.
如果你想重复一些障碍,只需增加 N
随你走:
If you want to do repeated barriers, just increment the N
as you go:
unsigned int lastBarrier = 0;
while(1) {
switch(task.id()) {
case TaskID::Barrier:
barrier(lastBarrier += processCount);
break;
}
}
这篇关于如何使用std :: atomic实现可重用的线程屏障的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!