【问题标题】:How to properly synchronize threads at barriers如何正确同步障碍处的线程
【发布时间】:2021-10-30 15:56:09
【问题描述】:

我遇到了一个问题,我很难判断我应该使用哪个同步原语。

我正在创建 n 个在内存区域上工作的并行线程,每个线程都分配给该区域的特定部分,并且可以独立于其他线程完成其任务。在某些时候我需要收集所有线程的工作结果,这是使用障碍的一个很好的例子,这就是我正在做的事情。

我必须使用 n 个工作线程之一来收集他们所有工作的结果,为此我在线程函数中的计算代码后面有以下代码:

if (pthread_barrier_wait(thread_args->barrier)) {
   // Only gets called on the last thread that goes through the barrier
   // This is where I want to collect the results of the worker threads
}

到目前为止一切顺利,但现在我陷入困境:上面的代码处于循环中,因为我希望线程在一定数量的循环自旋中再次完成工作。这个想法是,每次pthread_barrier_wait 解除阻塞都意味着所有线程都完成了它们的工作,并且循环/并行工作的下一次迭代可以重新开始。

这样做的问题是结果收集器块语句不能保证在其他线程再次开始在该区域上工作之前执行,因此存在竞争条件。我正在考虑使用这样的 UNIX 条件变量:

// This code is placed in the thread entry point function, inside
// a loop that also contains the code doing the parallel
// processing code.

if (pthread_barrier_wait(thread_args->barrier)) {
    // We lock the mutex
    pthread_mutex_lock(thread_args->mutex);
    collectAllWork(); // We process the work from all threads
    // Set ready to 1
    thread_args->ready = 1;
    // We broadcast the condition variable and check it was successful
    if (pthread_cond_broadcast(thread_args->cond)) {
        printf("Error while broadcasting\n");
        exit(1);
    }
    // We unlock the mutex
    pthread_mutex_unlock(thread_args->mutex);
} else {
    // Wait until the other thread has finished its work so
    // we can start working again
    pthread_mutex_lock(thread_args->mutex);
    while (thread_args->ready == 0) {
        pthread_cond_wait(thread_args->cond, thread_args->mutex);
    }
    pthread_mutex_unlock(thread_args->mutex);
}

这有多个问题:

  • 出于某种原因,pthread_cond_broadcast 永远不会解锁等待pthread_cond_wait 的任何其他线程,我不知道为什么。
  • 如果一个线程pthread_cond_waits 收集器线程广播之后会发生什么?我相信while (thread_args->ready == 0)thread_args->ready = 1 可以防止这种情况发生,但请看下一点...
  • 在下一个循环旋转时,ready 仍将设置为1,因此没有线程将再次调用pthread_cond_wait。我看不到任何地方可以将ready 正确设置回0:如果我在pthread_cond_wait 之后的else 块中执行此操作,则有可能另一个没有等待条件的线程读取@987654335 @ 并开始等待,即使我已经从 if 块广播了。

请注意,我需要为此使用障碍。

我该如何解决这个问题?

【问题讨论】:

    标签: c multithreading unix condition-variable barrier


    【解决方案1】:

    您可以使用两个障碍(工作和收集器):

    while (true) {
    
        //do work
    
        //every thread waits until the last thread has finished its work
        if (pthread_barrier_wait(thread_args->work_barrier)) {
            //only one gets through, then does the collecting
            collectAllWork();
        }
    
        //every thread will wait until the collector has reached this point
        pthread_barrier_wait(thread_args->collect_barrier);
    
    }
    

    【讨论】:

    • 简单有效!谢谢!
    【解决方案2】:

    你可以使用一种双缓冲

    每个工作人员将有两个存储槽用于存储结果。 在屏障之间,工人将他们的结果存储到一个槽,而收集器将从 另一个槽读取结果。

    这种方法有几个优点:

    • 没有额外的障碍
    • 无条件队列
    • 无锁定
    • 槽标识符甚至不必是原子的,因为每个线程都可以拥有它自己的副本并在遇到障碍时切换它
    • 当收集器处理另一个槽时,工作人员可以工作的性能更高

    示例工作流程:

    迭代 1。

    • worker 写入 slot 0
    • 收集器什么都不做,因为没有数据准备好
    • 全部等待屏障

    迭代 2。

    • worker 写入 slot 1
    • 收集器从插槽 0 读取
    • 全部等待屏障

    迭代 3.

    • worker 写入 slot 0
    • 收集器从插槽 1 读取
    • 全部等待屏障

    迭代 4.

    • 进入迭代 2

    【讨论】:

    • 谢谢!在这种情况下,您必须将一个线程用作工作线程,我对吗?虽然我没有奖励您的回答,但我不得不承认这可能更接近我最初处理该问题的方式,但我的问题是我被要求遵循非常具体的要求来解决此任务,所以我不认为我可以采取这种方法。我仍然会记住它以备将来使用。
    猜你喜欢
    • 2011-04-16
    • 2012-01-05
    • 2016-03-02
    • 2012-07-26
    • 1970-01-01
    • 2016-10-31
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    相关资源
    最近更新 更多