linux下的同步與互斥 談到linux的並發,必然涉及到線程之間的同步和互斥,linux主要為我們提供了幾種實現線程間同步互斥的 機制,本文主要介紹互斥鎖,條件變數和訊號量。互斥鎖和條件變數包含在pthread線程庫中,使用時需要包含 <pthread.h>標頭檔。而使用訊號量時需要包含<semaphore.h>標頭檔。 1.互斥鎖 型別宣告:pthread_mutex_t mutex; 對互斥量的初始化: 程式在使用pthread_mutex_t之前需要先對其進行初始化,對於靜態分配的pthread_mutex_t變數來說, 只要將PTHREAD_MUTEX_INITIALIZER賦給變數就行了,語句如下: static pthread_mutex_t mutex=PTHREAD_mutex_INITIALIZER; 而對於動態分配或沒有預設互斥屬性的互斥變數來說,要調用pthread_mutex_init函數來執行初始化工 作。函式宣告如下: int pthread_mutex_init(pthread_mutex_t* mutex,const pthread_mutexattr_t* attr); mutex是指向一個互斥量的指標,attr是指向一個屬性結構體的指標,為NULL時使用預設屬性。 需要注意的是pthread_mutex_init函數只能恰好執行一次,重複執行的結果是未定義的。 對互斥量的操作: int pthread_mutex_lock(pthread_mutex_t* mutex);//對互斥量執行鎖定操作 int pthread_mutex_unlock(pthread_mutex_t* mutex);//對互斥量執行解鎖操作 對互斥量的銷毀: int pthread_mutex_destroy(pthread_mutex_t* mutex);// 使用方法: 以下代碼用互斥量來保護一個臨界區: pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//初始化 pthread_mutex_lock(&mutex);//獲得鎖 /*critical section code*/ pthread_mutex_unlock(&mutex);//釋放鎖 通常比較適用的方法是將編寫一組訪問臨界區的函數,將鎖定調用都放在這些函數中,這是確保對對象的拍他 性訪問的一種方法,這樣鎖定機制對調用線程就是透明的了。 2.條件變數 有時,我們可能需要讓線程在某個條件滿足之前一直等待,在未使用條件變數之前,您可能會使用忙等待,如 下列代碼: while(x!=y); 這樣的方式,然而這種佔著茅坑不拉屎的行為是非常不被提倡的。因此,我們引入條件變數的概念來是線程在 某個條件不滿足是進入掛起狀態。 型別宣告:pthread_cond_t cond; 對條件變數的初始化: 程式在使用pthread_cond_t變數之前必須對其進行初始化。對於靜態分配的pthread_cond_t變數來說, 只要將PTHREAD_COND_INITIALIZER賦給變數就星了,語句如下: pthread_cond_t cond=PTHREAD_COND_INITIALIZER; 對於動態分配的或沒有預設屬性的變數來說,就要調用pthread_cond_init函數來執行初始化。函式宣告如下: int pthread_cond_init(pthread_cond_t* cond,const pthread_cond_attr_t* attr); cond是指向一個條件變數的指標,attr是指向一個屬性結構體的指標,為NULL時使用預設屬性。 對條件變數的操作: int pthread_cond_wait(pthread_cond_* cond,pthread_mutex_t* mutex);//使線程阻塞於某個條件。 int pthread_cond_signal(pthread_cond_t* cond);//喚醒某個阻塞在cond上的線程。 對條件變數的銷毀: int pthread_cond_destroy(pthread_cond_t* cond); 使用方法: 條件變數是與斷言或條件的測試一同調用的,以此來實現將條件變數關聯到某個斷言上去。通常線程會對 一個條件進行測試,如果失敗,就會調用pthread_cond_wait函數將線程阻塞。範例程式碼如下: pthread_mutex_lock(&mutex); while( a<b ) pthread_cond_wait(&cond,&mutex); pthread_mutex_unlock(&mutex); 調用線程應該在它測試或調用pthread_cond_wait之前獲得一個互斥量,以避免在測試條件的時候有其他 線程改寫條件中變數的值。pthread_cond_wait函數中第二個參數是一個互斥量類型的指標,線程在調用 pthread_cond_wait後會隱式的原子地釋放mutex互斥量並阻塞,允許其他線程獲得互斥量並修改斷言中 的變數。當線程成功的從pthread_cond_wait中返回時,它就再次擁有了互斥量,並且不用顯式的重新獲 得互斥量。 當其他線程修改了斷言中變數的值後可以調用pthread_cond_signal函數來喚醒一個等待在某個斷言成真 的線程。也可以使用pthread_cond_broadcast(pthread_cond_t*)函數來喚醒所有等待在某個條件變數 上的線程。在修改斷言中出現的任一變數之前要獲得互斥量。範例程式碼如下: pthread_mutex_lock(&mutex); a++; pthread_cond_signal(&cond);//pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mutex); 3.訊號量 訊號量是一個整型變數,它帶有兩個原子操作wait和signal。wait操作還可以被稱為down、P操作。signal操 作還可以被稱為up、V、post操作。 如果S大於0,wait操作就在一個原子操作中對其進行減量運算。如果S等於0,wait操作就就在一個原子操作中 測試S,阻塞調用程式,將調用程式放入wait的等待隊列中。 如果有線程在訊號量上阻塞,則S必然等於0,signal操作就會解除對某一個等待線程的阻塞。如果S大於0,即 沒有線程阻塞在訊號量上,signal就對S進行增量操作。 可以把訊號量理解為臨界區中資源的可用數量,wait表示對資源的申請,當沒有可用資源時訊號量為0,signal 表示線程使用資源後,對資源的釋放。 下面介紹幾種通過訊號量來控制線程按某種順序執行的方法: 1.線程1中a先於線程2中b執行[S初始話為0] Process 1: a; signal(&S); Process 2: wait(&S); b; 2.線程1中a於線程2中b語句交替執行[S,Q初始化為1] Process 1: while(1){ wait(&S); a; signal(&Q); } Process 2: while(1){ wait(&Q); b; signal(&S); } 可通過讓S為0Q為1,或讓S為1Q為0,來保證讓a或b先執行。 需要注意的是,為了避免申請多個資源發生死結,應按照相同的順序申請資源,這裡公司面試的時候經常會被 問到。 linux中訊號量有無名訊號量和命名訊號量之分,無名訊號量可用於線程之間的同步和互斥,命名訊號量可用 於進程間的通訊,命名訊號量與具名管道相似,以檔案的形式儲存於磁碟上。 無名訊號量: 型別宣告: sem_t sem; 初始化: int sem_init(sem_t* sem,int pshared,int value); 參數pshared為0,表示訊號量只能由初始化這個訊號量的進程中的線程使用。 value表示要將sem初始化的值。value不能為負。 操作: int sem_post(sem_t* sem);//signal操作 int sem_wait(sem_t* sem);//wait操作 銷毀: int sem_destroy(sem_t* sem); 使用方法: 通常先由主線程調用sem_init對訊號量進行初始化。然後在其他線程中調用post或wait函數。範例程式碼 如下: static sem_t sem; sem_init(&sem,0,1); sem_wait(&sem); /*critical section*/ sem_post(&sem); 命名訊號量: 型別宣告: sem_t sem; 初始化: sem_t* sem_open(const char* name,int oflag,...); 參數oflag用來確定是建立訊號量,還是僅僅由函數對其訪問。若oflag中的O_CREAT位元位被設定,則需 要另外兩個參數,mode_t mode為檔案許可權,unsigned value為訊號量值。 關閉: int sem_close(sem_t* sem); 刪除: int sem_unlink(sem_t* sem); 操作方式與無名訊號量相同。