本文可任意轉載,但必須註明作者和出處。
【原創】手把手教你Linux下的多線程設計(三)
--Linux下多線程編程詳解
原創作者:Frozen_socker(冰棍)
E_mail:dlskyfly@163.com
線程互斥
互斥操作,就是對某段代碼或某個變數修改的時候只能有一個線程在執行這段代碼,其他線程不能同時進入這段代碼或同時修改該變數。這個代碼或變數稱為臨界資源。
例如:有兩個線程A和B,臨界資源為X,首先線程A進入,將X置為加鎖狀態,在A將鎖開啟之前的這段時間裡,如果此時恰巧線程B也欲獲得X,但它發現X處於加鎖狀態,說明有其它線程正在執行互斥部分,於是,線程B將自身阻塞。。。線程A處理完畢,在退出前,將X解鎖,並將其它線程喚醒,於是線程B開始對X進行加鎖操作了。通過這種方式,實現了兩個不同線程的交替操作。
記住:一個互斥體永遠不可能同時屬於兩個線程。或者處於鎖定狀態;或者空閑中,不屬於任何一個線程。
代碼如下:
//example_3.c
#include <stdio.h>
#include <pthread.h>
void * pthread_func_test(void * arg);
pthread_mutex_t mu;
int main()
...{
int i;
pthread_t pt;
pthread_mutex_init(&mu,NULL); //聲明mu使用預設屬性,此行可以不寫
pthread_create(&pt,NULL,pthread_func_test,NULL);
for(i = 0; i < 3; i++)
...{
pthread_mutex_lock(&mu);
printf("主線程ID是:%lu ",pthread_self()); //pthread_self函數作用:獲得當前線程的id
pthread_mutex_unlock(&mu);
sleep(1);
}
}
void * pthread_func_test(void * arg)
...{
int j;
for(j = 0; j < 3; j++)
...{
pthread_mutex_lock(&mu);
printf("新線程ID是:%lu ",pthread_self());
pthread_mutex_unlock(&mu);
sleep(1);
}
}
終端輸出結果:
主線程ID是 : 3086493376
新線程ID是 : 3086490512
主線程ID是 : 3086493376
新線程ID是 : 3086490512
主線程ID是 : 3086493376
新線程ID是 : 3086490512
註:在你機器上啟動並執行結果很可能與這裡顯示的不一樣。
pthread_mutex_lock聲明開始用互斥鎖上鎖,此後的代碼直至調用pthread_mutex_unlock為止,都處於加鎖狀態中,即同一時間只能被一個線程調用執行。當另一個線程執行到pthread_mutex_lock處時,如果該鎖此時被其它線程使用,那麼該線程被阻塞,即程式將等待到其它線程釋放此互斥鎖。
上述例子中,涉及到了幾個函數:pthread_mutex_init/pthread_mutex_lock/pthread_mutex_unlock/pthread_mutex_destroy/pthread_self
函數原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
函數作用:
初始化互斥體類型變數mutex,變數的屬性由attr進行指定。attr設為NULL,即採用預設屬性,這種方式與pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER的方式等價。
函數原型:
int pthread_mutex_lock(pthread_mutex_t *mutex);
函數作用:
用來鎖住互斥體變數。如果參數mutex所指的互斥體已經被鎖住了,那麼發出調用的線程將被阻塞直到其他線程對mutex解鎖為止。
函數原型:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
函數作用:
如果當前的線程擁有參數mutex所指定的互斥體,那麼該函數調用將該互斥體解鎖。
函數原型:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
函數作用:
用來釋放互斥體所佔用的資源。
函數原型:
pthread_t pthread_self(void);
函數作用:獲得線程自身的ID。前面我們已經提到過,pthread_t的類型為unsigned long int,所以在列印的時候要使用%lu方式,否則將產生奇怪的結果。
但是上面的代碼並不完善,假設將迴圈次數修改得足夠的長,列印後的結果可能並不是我們所希望看到的交替列印,可能象下面這樣:
主線程ID是 : 3086493376
新線程ID是 : 3086490512
主線程ID是 : 3086493376
新線程ID是 : 3086490512
新線程ID是 : 3086490512
主線程ID是 : 3086493376
這是什麼原因呢?因為Linux是分時作業系統,採用的是時間片輪轉的方式,主線程和新線程可能因為其它因素的幹擾,獲得了非順序的時間片。如果想要嚴格的做到“交替”方式,可以略施小計,即加入一個標誌。
完整程式如下:
//example_4.c
#include <stdio.h>
#include <pthread.h>
void * pthread_func_test(void * arg);
pthread_mutex_t mu;
int flag = 0;
int main()
...{
int i;
pthread_t pt;
pthread_mutex_init(&mu,NULL);
pthread_create(&pt,NULL,pthread_func_test,NULL);
for(i = 0; i < 3; i++)
...{
pthread_mutex_lock(&mu);
if(flag == 0)
printf("主線程ID是:%lu ",pthread_self());
flag = 1;
pthread_mutex_unlock(&mu);
sleep(1);
}
pthread_join(pt, NULL);
pthread_mutex_destroy(&mu);
}
void * pthread_func_test(void * arg)
...{
int j;
for(j = 0; j < 3; j++)
...{
pthread_mutex_lock(&mu);
if(flag == 1)
printf("新線程ID是:%lu ",pthread_self());
flag == 0;
pthread_mutex_unlock(&mu);
sleep(1);
}
}
在使用互斥鎖的過程中很有可能會出現死結:即兩個線程試圖同時佔用兩個資源,並按不同的次序鎖定相應的互斥鎖,例如兩個線程都需要鎖定互斥鎖1和互斥鎖2,A線程先鎖定互斥鎖1,B線程先鎖定互斥鎖2,這時就出現了死結。此時我們可以使用函數pthread_mutex_trylock,該函數企圖鎖住一個互斥體,但不阻塞。
函數原型:
int pthread_mutex_trylock(pthread_mutex_t *mutex);
函數pthread_mutex_trylock()用來鎖住參數mutex所指定的互斥體。如果參數mutex所指的互斥體已經被上鎖,該調用不會阻塞等待互斥體的解鎖,而會返回一個錯誤碼。通過對傳回碼的判斷,程式員就可以針對死結做出相應的處理。所以在對多個互斥體編程中,尤其要注意這一點。
經過以上的講解,我們就學習了Linux下關於多線程方面對互斥體變數的操作。下一節,將給大家講解有關線程同步方面的知識點。