1. 程式人生 > >7.10-第十課:執行緒同步

7.10-第十課:執行緒同步

================ 第十課  執行緒同步 ================
一、競爭與同步 --------------
當多個執行緒同時訪問其所共享的程序資源時, 需要相互協調,以防止出現數據不一致、 不完整的問題。這就叫執行緒同步。
範例:vie.c
理想中的原子++:
-----------------+-----------------+------        執行緒1     |       執行緒2     | 記憶體 --------+--------+--------+--------+------  指  令 | 暫存器 | 指  令 | 暫存器 | g_cn --------+--------+--------+--------+------  讀記憶體 |    0   |        |        |   0  算加法 |    1   |        |        |   0  寫記憶體 |    1   |        |        |   1         |        | 讀記憶體 |    1   |   1         |        | 算加法 |    2   |   1         |        | 寫記憶體 |    2   |   2 --------+--------+--------+--------+------
現實中的非原子++:
-----------------+-----------------+------        執行緒1     |       執行緒2     | 記憶體 --------+--------+--------+--------+------  指  令 | 暫存器 | 指  令 | 暫存器 | g_cn --------+--------+--------+--------+------  讀記憶體 |    0   |        |        |   0         |        | 讀記憶體 |    0   |   0  算加法 |    1   |        |        |   0         |        | 算加法 |    1   |   0  寫記憶體 |    1   |        |        |   1         |        | 寫記憶體 |    1   |   1 --------+--------+--------+--------+------
二、互斥量 ----------
int pthread_mutex_init (pthread_mutex_t* mutex,     const pthread_mutexattr_t* mutexattr);
亦可
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_lock (pthread_mutex_t* mutex);
int pthread_mutex_unlock (pthread_mutex_t* mutex);
int pthread_mutex_destroy (pthread_mutex_t* mutex);
1) 互斥量被初始化為非鎖定狀態;
2) 執行緒1呼叫pthread_mutex_lock函式,立即返回,    互斥量呈鎖定狀態;
3) 執行緒2呼叫pthread_mutex_lock函式,阻塞等待;
4) 執行緒1呼叫pthread_mutex_unlock函式,    互斥量呈非鎖定狀態;
5) 執行緒2被喚醒,從pthread_mutex_lock函式中返回, 互斥量呈鎖定狀態;
...
範例:mutex.c
三、訊號量 ----------
訊號量是一個計數器,用於控制訪問有限共享資源的執行緒數。
#include <semaphore.h>
// 建立訊號量 int sem_init (sem_t* sem, int pshared,     unsigned int value);
sem     - 訊號量ID,輸出。
pshared - 一般取0,表示呼叫程序的訊號量。           非0表示該訊號量可以共享記憶體的方式,           為多個程序所共享(Linux暫不支援)。
value   - 訊號量初值。
// 訊號量減1,不夠減即阻塞 int sem_wait (sem_t* sem);
// 訊號量減1,不夠減即返回-1,errno為EAGAIN int sem_trywait (sem_t* sem);
// 訊號量減1,不夠減即阻塞, // 直到abs_timeout超時返回-1,errno為ETIMEDOUT int sem_timedwait (sem_t* sem,     const struct timespec* abs_timeout);
struct timespec {     time_t tv_sec;  // Seconds     long   tv_nsec; // Nanoseconds [0 - 999999999] };
// 訊號量加1 int sem_post (sem_t* sem);
// 銷燬訊號量 int sem_destroy (sem_t* sem);
範例:sem.c
注意:
1) 訊號量APIs沒有宣告在pthread.h中,    而是宣告在semaphore.h中,失敗也不返回錯誤碼,    而是返回-1,同時設定errno。
2) 互斥量任何時候都只允許一個執行緒訪問共享資源,    而訊號量則允許最多value個執行緒同時訪問共享資源,    當value為1時,與互斥量等價。
範例:pool.c
四、死鎖問題 ------------
執行緒1   執行緒2   |       | 獲取A   獲取B   |       | 獲取B   獲取A <- 死鎖      \ / 釋放B X 釋放A      / \ 釋放A   釋放B
範例:dead.c
五、條件變數 ------------
生產者消費者模型
生產者:產生資料的執行緒。 消費者:使用資料的執行緒。
通過緩衝區隔離生產者和消費者,與二者直連相比, 避免相互等待,提高執行效率。
生產快於消費,緩衝區滿,撐死。 消費快於生產,緩衝區空,餓死。
條件變數可以讓呼叫執行緒在滿足特定條件的情況下暫停。
int pthread_cond_init (pthread_cond_t* cond,     const pthread_condattr_t* attr);
亦可
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// 使呼叫執行緒睡入條件變數cond,同時釋放互斥鎖mutex int pthread_cond_wait (pthread_cond_t* cond,     pthread_mutex_t* mutex);
int pthread_cond_timedwait (pthread_cond_t* cond,     pthread_mutex_t* mutex,     const struct timespec* abstime);
struct timespec {     time_t tv_sec;  // Seconds     long   tv_nsec; // Nanoseconds [0 - 999999999] };
// 從條件變數cond中喚出一個執行緒, // 令其重新獲得原先的互斥鎖 int pthread_cond_signal (pthread_cond_t* cond);
注意:被喚出的執行緒此刻將從pthread_cond_wait函式中返回, 但如果該執行緒無法獲得原先的鎖,則會繼續阻塞在加鎖上。
// 從條件變數cond中喚出所有執行緒 int pthread_cond_broadcast (pthread_cond_t* cond);
int pthread_cond_destroy (pthread_cond_t* cond);
範例:cond.c
注意:當一個執行緒被從條件變數中喚出以後, 導致其睡入條件變數的條件可能還需要再判斷一次, 因其隨時有可能別其它執行緒修改。
範例:bc.c (if->while)
六、哲學家就餐問題 ------------------
1965年,著名電腦科學家艾茲格·迪科斯徹,提出並解決 了一個他稱之為哲學家就餐的同步問題。從那時起,每個發 明同步原語的人,都希望通過解決哲學家就餐問題來展示其 同步原語的精妙之處。
這個問題可以簡單地描述如下:
五個哲學家圍坐在一張圓桌周圍,每個哲學家面前都有一盤 通心粉。由於通心粉很滑,所以需要兩把叉子才能夾住。相 鄰兩個盤子之間放有一把叉子。哲學家的生活中有兩種交替 活動時段:即吃飯和思考。當一個哲學家覺得餓了時,他就 試圖分兩次去取其左邊和右邊的叉子,每次拿一把,但不分 次序。如果成功地得到了兩把叉子,就開始吃飯,吃完後放 下叉子繼續思考。
圖示:dining.png
關鍵問題是:能為每一個哲學家寫一段描述其行為的程式, 且決不會死鎖嗎?
提示:
如果五位哲學家同時拿起左面的叉子,就沒有人能夠拿到他 們各自右面的叉子,於是發生了死鎖。
如果每位哲學家在拿到左面的叉子後,發現其右面的叉子不 可用,那麼就先放下左面的叉的,等待一段時間,再重複此 過程。可能在某一個瞬間,所有的哲學家都同時拿起左叉, 看到右叉不可用,又都放下左叉,等一會兒,又都同時拿起 左叉,如此重複下去。雖然程式在不停執行,但都無法取得 進展,於是發生了活鎖。
思路:
解決問題的關鍵在於,必須保證任意一位哲學家只有在其左 右兩個鄰居都沒有在進餐時,才允許其進入進餐狀態。這樣 做不僅不會發生死鎖,而且對於任意數量的哲學家都能獲得 最大限度的並行性。
範例:dining.c

來自為知筆記(Wiz)