The Little Book of Semaphores 訊號量小書 第九章 C中的同步
第九章 C中的同步
在本節中,我們將在C中編寫一個多執行緒的同步程式。附錄B提供了一些實用程式程式碼,用於使C程式碼更加可口。 本節中的示例依賴於該程式碼。
9.1 互斥
我們首先定義一個包含共享變數的結構體:
counter是一個共享變數,它將由併發執行緒遞增,直到它達到end。 我們將使用陣列通過在每次遞增後跟蹤counter的值來檢查同步錯誤。
9.1.1 父執行緒程式碼
以下是父執行緒用於建立執行緒並等待它們完成的程式碼:
第一個迴圈建立子執行緒; 第二個迴圈等待它們完成。 當最後一個子執行緒完成時,父執行緒會呼叫check_array來檢查錯誤。 make_thread和join_thread在附錄B中定義。
9.1.2 子執行緒程式碼
以下是每個子執行緒執行的程式碼:
每次迴圈時,子執行緒使用計數器counter作為陣列array的索引並遞增相應的元素。 然後他們增加計數器counter並檢查它們是否完成。
9.1.3 同步錯誤
如果一切正常,陣列的每個元素都應該遞增一次。 因此,為了檢查錯誤,我們可以計算不是1的元素數量:
您可以從greenteapress.com/semaphores/counter.c下載該程式(包括清理程式碼)
如果您編譯並執行該程式,您應該看到如下輸出:
當然,子執行緒之間的互動取決於作業系統的詳細資訊,以及計算機上執行的其他程式。在本文所示的示例中,一個執行緒在另一個執行緒啟動之前從0一直執行到結束,所以沒有同步錯誤也就不足為奇了。
但隨著end變得越來越大,子執行緒之間的上下文切換也越來越多。 在我的系統中,當end為100,000,000時,我開始看到錯誤。
思考:使用訊號量強制對共享變數進行獨佔訪問,然後再次執行程式以確認沒有錯誤。
9.1.4 互斥提示
以下是我的解決方案中使用的Shared版本
第5行將mutex宣告為訊號量; 第20行使用數值1來初始化mutex。
9.1.5 互斥方案
以下是子執行緒程式碼的同步版本:
這裡沒有什麼太令人驚訝的了; 唯一棘手的事情是記住在return語句之前釋放互斥鎖。
您可以從greenteapress.com/semaphores/counter_mutex.c下載此解決方案。
9.2 製作自己的訊號量
對於使用Pthreads的程式,最常用的同步工具是互斥鎖和條件變數,而不是訊號量。有關這些工具的解釋,我推薦Butenhof的《POSIX Threads程式設計》[2]。
思考:閱讀有關互斥鎖和條件變數的內容,然後使用它們編寫訊號量的實現。
您可能希望在解決方案中使用以下實用程式程式碼。 這是我封裝的Pthreads互斥量:
以及我封裝的Pthread條件變數:
9.2.1 訊號量實現提示
這是我用於訊號量的結構體定義:
value是訊號量的值。 wakeups計算未決訊號的數量; 也就是說,已被喚醒但尚未恢復執行的執行緒數。 喚醒的原因是為了確保我們的訊號量具有第4.3節中描述的屬性3。
mutex提供對value和wakeups的獨佔訪問; cond是執行緒等待的條件變數,如果他們等待訊號量的話。
以下是此結構的初始化程式碼:
9.2.2 訊號量實現
這是我使用Pthread的互斥量和條件變數實現的訊號量:
大部分都是直截了當的; 唯一可能棘手的是在第7行的do ... while迴圈。這是使用條件變數的一種不尋常的方式,但在這種情況下它是必要的。
思考:為什麼我們不能用while迴圈替換這個do... while迴圈?
9.2.3 訊號量實現細節
使用while迴圈,此實現將不具有屬性3。一個執行緒可能會發出訊號,然後執行回來並捕獲它自己的訊號。
使用do ... while迴圈,可以保證,當一個執行緒發出訊號時,另一個等待的執行緒將得到訊號,即使另一個執行緒在其中一個等待執行緒恢復之前獲得第3行的互斥鎖。
【好吧,差不多。 事實證明,適時的虛假喚醒(參見http://en.wikipedia.org/wiki/Spurious_wakeup)可能會違反此保證。】