# 同步,第 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`,那么可以保證它們在同一個迭代中。
- UIUC CS241 系統編程中文講義
- 0. 簡介
- #Informal 詞匯表
- #Piazza:何時以及如何尋求幫助
- 編程技巧,第 1 部分
- 系統編程短篇小說和歌曲
- 1.學習 C
- C 編程,第 1 部分:簡介
- C 編程,第 2 部分:文本輸入和輸出
- C 編程,第 3 部分:常見問題
- C 編程,第 4 部分:字符串和結構
- C 編程,第 5 部分:調試
- C 編程,復習題
- 2.進程
- 進程,第 1 部分:簡介
- 分叉,第 1 部分:簡介
- 分叉,第 2 部分:Fork,Exec,等等
- 進程控制,第 1 部分:使用信號等待宏
- 進程復習題
- 3.內存和分配器
- 內存,第 1 部分:堆內存簡介
- 內存,第 2 部分:實現內存分配器
- 內存,第 3 部分:粉碎堆棧示例
- 內存復習題
- 4.介紹 Pthreads
- Pthreads,第 1 部分:簡介
- Pthreads,第 2 部分:實踐中的用法
- Pthreads,第 3 部分:并行問題(獎金)
- Pthread 復習題
- 5.同步
- 同步,第 1 部分:互斥鎖
- 同步,第 2 部分:計算信號量
- 同步,第 3 部分:使用互斥鎖和信號量
- 同步,第 4 部分:臨界區問題
- 同步,第 5 部分:條件變量
- 同步,第 6 部分:實現障礙
- 同步,第 7 部分:讀者編寫器問題
- 同步,第 8 部分:環形緩沖區示例
- 同步復習題
- 6.死鎖
- 死鎖,第 1 部分:資源分配圖
- 死鎖,第 2 部分:死鎖條件
- 死鎖,第 3 部分:餐飲哲學家
- 死鎖復習題
- 7.進程間通信&amp;調度
- 虛擬內存,第 1 部分:虛擬內存簡介
- 管道,第 1 部分:管道介紹
- 管道,第 2 部分:管道編程秘密
- 文件,第 1 部分:使用文件
- 調度,第 1 部分:調度過程
- 調度,第 2 部分:調度過程:算法
- IPC 復習題
- 8.網絡
- POSIX,第 1 部分:錯誤處理
- 網絡,第 1 部分:簡介
- 網絡,第 2 部分:使用 getaddrinfo
- 網絡,第 3 部分:構建一個簡單的 TCP 客戶端
- 網絡,第 4 部分:構建一個簡單的 TCP 服務器
- 網絡,第 5 部分:關閉端口,重用端口和其他技巧
- 網絡,第 6 部分:創建 UDP 服務器
- 網絡,第 7 部分:非阻塞 I O,select()和 epoll
- RPC,第 1 部分:遠程過程調用簡介
- 網絡復習題
- 9.文件系統
- 文件系統,第 1 部分:簡介
- 文件系統,第 2 部分:文件是 inode(其他一切只是數據...)
- 文件系統,第 3 部分:權限
- 文件系統,第 4 部分:使用目錄
- 文件系統,第 5 部分:虛擬文件系統
- 文件系統,第 6 部分:內存映射文件和共享內存
- 文件系統,第 7 部分:可擴展且可靠的文件系統
- 文件系統,第 8 部分:從 Android 設備中刪除預裝的惡意軟件
- 文件系統,第 9 部分:磁盤塊示例
- 文件系統復習題
- 10.信號
- 過程控制,第 1 部分:使用信號等待宏
- 信號,第 2 部分:待處理的信號和信號掩碼
- 信號,第 3 部分:提高信號
- 信號,第 4 部分:信號
- 信號復習題
- 考試練習題
- 考試主題
- C 編程:復習題
- 多線程編程:復習題
- 同步概念:復習題
- 記憶:復習題
- 管道:復習題
- 文件系統:復習題
- 網絡:復習題
- 信號:復習題
- 系統編程笑話