在C中实现线程障碍和屏障重置的正确方法是什么?

时间:2014-10-07 08:29:58

标签: c multithreading x86 barrier

我试图在我的代码中实现一个简单的屏障,如下所示:

void waitOnBarrier(int* barrier, int numberOfThreads) {
    atomicIncrement(barrier); // atomic increment implemented in assembly
    while(*barrier < numberOfThreads);
}

然后在代码中使用屏障:

int g_barrier = 0; // a global variable

waitOnBarrier(&g_barrier, someKnownNumberOfThreads);

到目前为止一切顺利,但我应该将 g_barrier 变量重置为零?如果我写的话

g_barrier = 0;

waitOnBarrier 调用之后,如果其中一个线程将从屏障中释放得比其他线程更快,并且在所有其他线程中使 g_barrier 无效,我将遇到问题仍在执行循环指令,因此最终他们将永远陷入障碍。

说明: waitOnBarrier 将编译成这样的东西(伪代码):

1: mov rax, numberOfThreads
2: mov rbx, [barrier]
3: cmp rax, rbx
4: jmp(if smaller) to 2

因此,如果我们在屏障上同步2个线程,并且 thread_1 在指令3或4的某处缓慢,而更快的 thread_2 到达屏障,则传递它继续 g_barrier 无效流程。这意味着在 thread_1 到达指令2后,它将在[barrier]处看到零值,并将永远停留在屏障上!

问题是,我应该如何取消 g_barrier ,它在代码中的位置是“足够远”,以至于我可以肯定到那时所有的线程都离开了障碍?或者是否有更正确的方法来实施障碍?

4 个答案:

答案 0 :(得分:3)

障碍实际上很难实施,主要原因是新服务员可以在所有旧服务员都有机会执行之前就开始到来,这排除了任何简单的基于计数的实施。我的首选解决方案是让屏障对象本身简单地指向存在于到达屏障的第一个线程的堆栈上的“当前屏障实例”,并且它也将是最后一个离开的线程(因为它不能离开而其他线程仍在引用其堆栈)。 Michael Burr对我过去关于该主题的问题的回答中包含了一个非常好的pthread原语(可以适应C11锁定原语或者你必须使用的任何东西)的示例实现:

https://stackoverflow.com/a/5902671/379897

是的,它看起来很多工作,但编写一个实际上满足障碍合同的屏障实现并非易事。

答案 1 :(得分:0)

尝试实施本书中解释的障碍解决方案:

The Little Book of Semaphores

答案 2 :(得分:0)

请勿将barrier变量重置为零。

当任何线程即将退出时,将barrier变量原子递减1。

您的屏障看起来不希望产生的工作线程数低于numberOfThreads

答案 3 :(得分:0)

我在尝试做类似的事情时遇到了这个问题,所以我想我会分享我的解决方案,万一其他人发现它有用。它是在纯C ++ 11中实现的(遗憾的是不是C11,因为标准的多线程部分在gcc和msvc中尚不支持)。

基本上,您维护两个计数器,其用法是交替使用的。以下是实现和使用示例:

    #include <cstdio>
    #include <thread>
    #include <condition_variable>

    // A barrier class; The barrier is initialized with the number of expected threads to synchronize
    class barrier_t{
        const size_t count;
        size_t counter[2], * currCounter;
        std::mutex mutex;
        std::condition_variable cv;

    public:
        barrier_t(size_t count) : count(count), currCounter(&counter[0]) {
            counter[0] = count;
            counter[1] = 0;
        }
        void wait(){
            std::unique_lock<std::mutex> lock(mutex);
            if (!--*currCounter){
                currCounter += currCounter == counter ? 1 : -1;
                *currCounter = count;
                cv.notify_all();
            }
            else {
                size_t * currCounter_local = currCounter;
                cv.wait(lock, [currCounter_local]{return *currCounter_local == 0; });
            }
        }
    };

    void testBarrier(size_t iters, size_t threadIdx, barrier_t *B){
        for(size_t i = 0; i < iters; i++){
            printf("Hello from thread %i for the %ith time!\n", threadIdx, i);
            B->wait();
        }
    }

    int main(void){
        const size_t threadCnt = 4, iters = 8;
        barrier_t B(threadCnt);
        std::thread t[threadCnt];   
        for(size_t i = 0; i < threadCnt; i++) t[i] = std::thread(testBarrier, iters, i, &B);
        for(size_t i = 0; i < threadCnt; i++) t[i].join();
        return 0;
    }