目錄
?
1.常見鎖概念
死鎖
死鎖四個必要條件
避免死鎖
避免死鎖算法
2. Linux線程同步
條件變量
同步概念與競態條件
條件變量函數 初始化
銷毀
等待條件滿足
喚醒等待
簡單案例:
條件變量使用規范
?
1.常見鎖概念
死鎖
死鎖是指在一組進程中的各個進程均占有不會釋放的資源,但因互相申請被其他進程所站用不會釋放的資源而處于的一種永久等待狀態。
死鎖四個必要條件
- 互斥條件:一個資源每次只能被一個執行流使用
- 請求與保持條件:一個執行流因請求資源而阻塞時,對已獲得的資源保持不放
- 不剝奪條件:一個執行流已獲得的資源,在末使用完之前,不能強行剝奪
- 循環等待條件:若干執行流之間形成一種頭尾相接的循環等待資源的關系
避免死鎖
- 破壞死鎖的四個必要條件
- 加鎖順序一致
- 避免鎖未釋放的場景
- 資源一次性分配
避免死鎖算法
- 死鎖檢測算法(了解)
核心思想:通過監控系統資源分配狀態,判斷是否存在死鎖的必要條件(尤其是循環等待),并在檢測到死鎖時觸發恢復機制。
- 銀行家算法(了解)
核心思想:通過預先判斷資源分配是否會導致系統進入 “不安全狀態”(可能引發死鎖的狀態),來拒絕或允許資源請求,確保系統始終處于 “安全狀態”。
2. Linux線程同步
條件變量
- 當一個線程互斥地訪問某個變量時,它可能發現在其它線程改變狀態之前,它什么也做不了。
- 例如一個線程訪問隊列時,發現隊列為空,它只能等待,只到其它線程將一個節點添加到隊列中。這種情況就需要用到條件變量。
同步概念與競態條件
- 同步:在保證數據安全的前提下,讓線程能夠按照某種特定的順序訪問臨界資源,從而有效避免饑餓問題,叫做同步
- 競態條件:因為時序問題,而導致程序異常,我們稱之為競態條件。在線程場景下,這種問題也不難理解
條件變量函數 初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); 參數: cond:要初始化的條件變量 attr:NULL
銷毀
int pthread_cond_destroy(pthread_cond_t *cond)
等待條件滿足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 參數: cond:要在這個條件變量上等待 mutex:互斥量,后面詳細解釋
喚醒等待
int pthread_cond_broadcast(pthread_cond_t *cond); 向所有等待在該條件變量上的線程發送通知,喚醒所有等待線程。這些線程會在獲取互斥鎖后繼續執行。 int pthread_cond_signal(pthread_cond_t *cond); 向至少一個等待在該條件變量上的線程發送通知,喚醒至少一個線程(具體喚醒哪個線程由調度器決定)。若有多個線程等待,通常只喚醒一個。
簡單案例:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> pthread_cond_t cond; pthread_mutex_t mutex; void *r1( void *arg ) { while ( 1 ){ pthread_cond_wait(&cond, &mutex); printf("活動\n"); } } void *r2(void *arg ) { while ( 1 ) { pthread_cond_signal(&cond); sleep(1); } } int main( void ) { pthread_t t1, t2; pthread_cond_init(&cond, NULL); pthread_mutex_init(&mutex, NULL); pthread_create(&t1, NULL, r1, NULL); pthread_create(&t2, NULL, r2, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); }
這段代碼的主要功能就是讓2個線程分別執行:一個一直等待,另一個每隔1秒喚醒它打印。
為什么 pthread_cond_wait 需要互斥量?
- 條件等待是線程間同步的一種手段,如果只有一個線程,條件不滿足,一直等下去都不會滿足,所以必須要有一個線程通過某些操作,改變共享變量,使原先不滿足的條件變得滿足,并且友好的通知等待在條件變量上的線程。
- 條件不會無緣無故的突然變得滿足了,必然會牽扯到共享數據的變化。所以一定要用互斥鎖來保護。沒有互斥鎖就無法安全的獲取和修改共享數據。
- 按照上面的說法,我們設計出如下的代碼:先上鎖,發現條件不滿足,解鎖,然后等待在條件變量上不就行了,如下代碼:
// 錯誤的設計 pthread_mutex_lock(&mutex); while (condition_is_false) { pthread_mutex_unlock(&mutex); //解鎖之后,等待之前,條件可能已經滿足,信號已經發出,但是該信號可能被錯過 pthread_cond_wait(&cond); pthread_mutex_lock(&mutex); } pthread_mutex_unlock(&mutex);
- 由于解鎖和等待不是原子操作。調用解鎖之后, 但是在調用pthread_cond_wait 之前,執行被切走了,切到了另一個signal通知線程,他獲取到互斥量,摒棄條件滿足,發送了信號,但是我們之前那個線程并沒有處于等待(還未調用),所以可以理解為等待隊列為空,那么我們回到原先的線程的時候再去調用?pthread_cond_wait 將錯過這個信號,可能會導致線程永遠阻塞在這個 pthread_cond_wait 。所以解鎖和等待必須是一個原子操作。
- 調用?
pthread_cond_wait()
?時,線程必須已持有互斥鎖?mutex
。函數會原子性地釋放該鎖,并將線程放入?cond
?的等待隊列。當其他線程發送信號(如調用?pthread_cond_signal()
)喚醒該線程時,pthread_cond_wait()
?會自動嘗試重新獲取鎖。線程只有在成功獲取鎖后才能從?pthread_cond_wait()
?返回,繼續執行后續代碼。條件變量使用規范
- 等待條件代碼
pthread_mutex_lock(&mutex); while (條件為假) pthread_cond_wait(cond, mutex); 修改條件 pthread_mutex_unlock(&mutex);
- 給條件發送信號代碼
pthread_mutex_lock(&mutex); 設置條件為真 pthread_cond_signal(cond); pthread_mutex_unlock(&mutex);
?