🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 同步,第 6 部分:实现障碍 > 原文:<https://github.com/angrave/SystemProgramming/wiki/Synchronization%2C-Part-6%3A-Implementing-a-barrier> ## 在继续下一步之前,如何等待 N 个线程达到某个点? 假设我们想要执行具有两个阶段的多线程计算,但我们不希望在第一阶段完成之前进入第二阶段。 我们可以使用称为 **barrier** 的同步方法。当一个线程到达一个屏障时,它将在屏障处等待,直到所有线程都到达屏障,然后它们将一起进行。 把它想象成和一些朋友一起去远足。你同意在每个山顶上等待对方(并且你会记下你的小组中有多少人)。说你是第一个到达第一座山顶的人。你会在那里等你的朋友。一个接一个,他们将到达顶部,但没有人会继续,直到你的小组中的最后一个人到达。一旦他们这样做,你们都会继续前进。 Pthreads 有一个实现它的函数`pthread_barrier_wait()`。你需要声明一个`pthread_barrier_t`变量并用`pthread_barrier_init()`初始化它。 `pthread_barrier_init()`将参与屏障的线程数作为参数。 [这是一个例子。](https://github.com/angrave/SystemProgramming/wiki/Sample-program-using-pthread-barriers) 现在让我们实现自己的障碍,并使用它来保持所有线程在大型计算中同步。 ```c double data[256][8192] 1 Threads do first calculation (use and change values in data) 2 Barrier! Wait for all threads to finish first calculation before continuing 3 Threads do second calculation (use and change values in data) ``` 螺纹功能有四个主要部分 - ```c void *calc(void *arg) { /* Do my part of the first calculation */ /* Am I the last thread to finish? If so wake up all the other threads! */ /* Otherwise wait until the other threads has finished part one */ /* Do my part of the second calculation */ } ``` 我们的主线程将创建 16 个线程,我们将每个计算分成 16 个单独的部分。每个线程都将被赋予一个唯一值(0,1,2,... 15),因此它可以在自己的块上工作。由于(void *)类型可以包含小整数,我们将通过将它转换为 void 指针来传递`i`的值。 ```c #define N (16) double data[256][8192] ; int main() { pthread_t ids[N]; for(int i = 0; i < N; i++) pthread_create(&ids[i], NULL, calc, (void *) i); ``` 注意,我们永远不会将此指针值取消引用作为实际的内存位置 - 我们只是将其直接转换回整数: ```c void *calc(void *ptr) { // Thread 0 will work on rows 0..15, thread 1 on rows 16..31 int x, y, start = N * (int) ptr; int end = start + N; for(x = start; x < end; x++) for (y = 0; y < 8192; y++) { /* do calc #1 */ } ``` 计算 1 完成后,我们需要等待较慢的线程(除非我们是最后一个线程!)。因此,请跟踪到达我们屏障的线程数量'checkpoint': ```c // Global: int remain = N; // After calc #1 code: remain--; // We finished if (remain ==0) {/*I'm last! - Time for everyone to wake up! */ } else { while (remain != 0) { /* spin spin spin*/ } } ``` 但是上面的代码有一个竞争条件(两个线程可能会尝试减少`remain`)并且循环是一个繁忙的循环。我们可以做得更好!让我们使用一个条件变量然后我们将使用广播/信号函数来唤醒睡眠线程。 提醒一下,条件变量类似于房子!线程去那里睡觉(`pthread_cond_wait`)。您可以选择唤醒一个线程(`pthread_cond_signal`)或所有线程(`pthread_cond_broadcast`)。如果当前没有线程正在等待,则这两个调用无效。 条件变量版本通常非常类似于忙循环不正确的解决方案 - 我们将在下面展示。首先,让我们添加一个互斥和条件全局变量,不要忘记在`main`中初始化它们...... ```c //global variables pthread_mutex_t m; pthread_cond_t cv; main() { pthread_mutex_init(&m, NULL); pthread_cond_init(&cv, NULL); ``` 我们将使用互斥锁来确保一次只有一个线程修改`remain`。最后到达的线程需要唤醒 _ 所有 _ 休眠线程 - 所以我们将使用`pthread_cond_broadcast(&cv)`而不是`pthread_cond_signal` ```c pthread_mutex_lock(&m); remain--; if (remain ==0) { pthread_cond_broadcast(&cv); } else { while(remain != 0) { pthread_cond_wait(&cv, &m); } } pthread_mutex_unlock(&m); ``` 当一个线程进入`pthread_cond_wait`时,它会释放互斥锁并休眠。在将来的某个时刻,它会被唤醒。一旦我们从睡眠中恢复一个线程,在返回之前它必须等到它可以锁定互斥锁。请注意,即使睡眠线程提前醒来,它也会检查 while 循环条件并在必要时重新进入等待状态。 **上面的障碍是不可重用的**意味着如果我们将它粘贴到任何旧的计算循环中,那么代码很可能会遇到障碍要么死锁,要么线程在一次迭代中前进更快的情况。考虑如何使上述障碍可重用,这意味着如果多个线程在循环中调用`barrier_wait`,那么可以保证它们在同一个迭代中。