如何在障碍处正确同步线程
How to properly synchronize threads at barriers
我遇到了一个问题,我很难确定应该使用哪个同步原语。
我正在创建 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_wait
s 在 收集器线程广播后会发生什么?我相信 while (thread_args->ready == 0)
和 thread_args->ready = 1
可以防止这种情况发生,但接下来请看下一点...
- 在下一个循环中,
ready
仍将设置为 1
,因此没有线程会再次调用 pthread_cond_wait
。我看不到任何可以将 ready
正确设置回 0
的地方:如果我在 pthread_cond_wait
之后的 else 块中这样做,则有可能另一个线程不是cond waiting yet reads 1
并开始等待,即使我已经从 if
块广播。
请注意,我需要为此使用障碍。
我该如何解决这个问题?
您可以使用两个障碍(工作和收集器):
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);
}
你可以使用一种双缓冲。
每个工作人员将有两个存储槽用于存储结果。
在屏障之间,工作人员会将他们的结果存储到一个插槽中,而收集器将从 另一个 插槽中读取结果。
这种方法有几个优点:
- 没有额外的障碍
- 没有条件队列
- 无锁定
- 插槽标识符甚至不必是原子的,因为每个线程都可以拥有自己的副本并在到达障碍时切换它
- 当收集器正在处理另一个槽时,工作人员可以工作的性能更高
示例工作流程:
迭代 1.
- 工作人员写入插槽 0
- 收集器什么都不做,因为没有数据准备好
- 全部等待关卡
迭代 2.
- worker 写入 slot 1
- 收集器从插槽 0 读取
- 全部等待关卡
迭代 3.
- 工作人员写入插槽 0
- 收集器从插槽 1 读取
- 全部等待关卡
迭代 4.
- 转到迭代 2
我遇到了一个问题,我很难确定应该使用哪个同步原语。
我正在创建 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_wait
s 在 收集器线程广播后会发生什么?我相信while (thread_args->ready == 0)
和thread_args->ready = 1
可以防止这种情况发生,但接下来请看下一点... - 在下一个循环中,
ready
仍将设置为1
,因此没有线程会再次调用pthread_cond_wait
。我看不到任何可以将ready
正确设置回0
的地方:如果我在pthread_cond_wait
之后的 else 块中这样做,则有可能另一个线程不是cond waiting yet reads1
并开始等待,即使我已经从if
块广播。
请注意,我需要为此使用障碍。
我该如何解决这个问题?
您可以使用两个障碍(工作和收集器):
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);
}
你可以使用一种双缓冲。
每个工作人员将有两个存储槽用于存储结果。 在屏障之间,工作人员会将他们的结果存储到一个插槽中,而收集器将从 另一个 插槽中读取结果。
这种方法有几个优点:
- 没有额外的障碍
- 没有条件队列
- 无锁定
- 插槽标识符甚至不必是原子的,因为每个线程都可以拥有自己的副本并在到达障碍时切换它
- 当收集器正在处理另一个槽时,工作人员可以工作的性能更高
示例工作流程:
迭代 1.
- 工作人员写入插槽 0
- 收集器什么都不做,因为没有数据准备好
- 全部等待关卡
迭代 2.
- worker 写入 slot 1
- 收集器从插槽 0 读取
- 全部等待关卡
迭代 3.
- 工作人员写入插槽 0
- 收集器从插槽 1 读取
- 全部等待关卡
迭代 4.
- 转到迭代 2