在介紹線程同步之前,我們需要先了解一下死鎖的相關概念。
死鎖:在一組程序中的各個程序均占有不會釋放的資源,但因互相申請被其他程序所占有用不會釋放的資源而處于一種永久等待的狀态。
無論線程還是程序都可能出現死鎖
死鎖的四個必要條件
- 互斥:每個資源每次隻能被一個執行流使用
- 請求與保持條件:一個執行流因請求資源而阻塞時,對已獲得的資源保持不放
- 不剝奪條件:一個執行流已獲得的資源在未使用完之前不能被強行剝奪
- 循環等待條件:若幹執行流之間形成一種頭尾相接的循環等待資源的關系
避免死鎖的方法
- 破壞死鎖的四個必要條件中的2,3,4條
- 加鎖的順序保持一緻即同步性
- 避免未釋放鎖的場景
- 資源一次性配置設定
線程同步
上一篇我們講了Linux線程的互斥,利用鎖的機制保證了線程的安全。本篇部落格我們将為大家講解Linux下的同步機制,因為時序問題,而導緻程式異常,我們稱之為競争狀态。而為了避免發生競争狀态,我們需要在保證資料安全的前提下,讓線程能夠按照某種特定的順序通路臨界資源稱為同步。
在OS中,我們提供互斥機制是為了保證資料安全性,同步機制為了保證合理性。
想要保證線程同步的合理性和高效性,我們就需要用到條件變量。當一個線程互斥地通路某個變量,它可能發現在其它線程改變狀态之前,它什麼也做不了。例如一個線程通路隊列時,發現隊列為空,它隻能等待,直到其它線程将一個節點添加到隊列中。這種情況就需要用到條件變量,下面我們來看看條件變量相關的函數,這些函數真的非常像互斥鎖。
- 初始化條件變量:一般定義全局變量并初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
//參數一:要初始化的條件變量 參數二:一般設定為NULL
- 銷毀
int pthread_cond_destroy(pthread_cond_t *cond)
- 在條件隊列中等待條件滿足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
//參數一為要等待的條件變量,參數二為一把互斥鎖,這把鎖的用途後面講解
- 喚醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);//喚醒所有的條件變量,驚群問題
int pthread_cond_signal(pthread_cond_t *cond); //喚醒某一個線程
使用條件變量例子:
此程式r2中的signal函數每隔1秒喚醒一次r1函數,是以每秒列印一次活動。
#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);
}

為什麼pthread_cond_wait函數參數需要傳互斥鎖:
pthread_mutex_lock(&mutex);
//進入臨界區通路臨界資源
while (condition_is_false) {
pthread_cond_wait(&cond);
}
pthread_mutex_unlock(&mutex);
- 從上面代碼中我們發現,當一個線程(可能是讀線程)進入臨界區時發現此時臨界資源不具備讀的條件,是以此時就需要另外一個線程對臨界資源進行寫操作,但是由于我們現在的讀線程手中拿着鎖,是以寫線程拿不到鎖,也就無法進入臨界區。為了解決這一問題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之前,如果已經有其他線程擷取到互斥量,摒棄條件滿足,發送了信号,那麼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);
下篇部落格我們就要使用條件變量來實作經典的生産者消費者模型。