在多線程編程中,引入了對象互斥鎖的概念,來保證共享數據操做的完整性。 每一個對象都對應於一個可稱爲" 互斥鎖" 的標記,這個標記用來保證在任一時刻, 只能有一個線程訪問該對象。html
互斥鎖也能夠叫線程鎖,接下來講說互斥鎖的的使用方法。編程
對互斥鎖進行操做的函數,經常使用的有以下幾個:安全
#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex); int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
對線程鎖進行操做的函數有不少,還包括許多線程鎖屬性的操做函數, 不過通常來講,對於並不複雜的狀況, 只須要使用建立、獲取鎖、釋放鎖、刪除鎖這幾個就足夠了。多線程
因此下面簡單看一下如何建立和使用互斥鎖。函數
在使用互斥鎖以前,須要先建立一個互斥鎖的對象。 互斥鎖的類型是 pthread_mutex_t ,因此定義一個變量就是建立了一個互斥鎖。測試
pthread_mutex_t mtx;
這就定義了一個互斥鎖。可是若是想使用這個互斥鎖仍是不行的,咱們還須要對這個互斥鎖進行初始化, 使用函數 pthread_mutex_init() 對互斥鎖進行初始化操做。ui
//第二個參數是 NULL 的話,互斥鎖的屬性會設置爲默認屬性 pthread_mutex_init(&mtx, NULL);
除了使用 pthread_mutex_init() 初始化一個互斥鎖,咱們還可使用下面的方式定義一個互斥鎖:this
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
在頭文件 /usr/include/pthread.h 中,對 PTHREAD_MUTEX_INITIALIZER 的聲明以下spa
# define PTHREAD_MUTEX_INITIALIZER \ { { 0, 0, 0, 0, 0, 0, { 0, 0 } } }
爲何能夠這樣初始化呢,由於互斥鎖的類型 pthread_mutex_t 是一個聯合體, 其聲明在文件 /usr/include/bits/pthreadtypes.h 中,代碼以下:線程
/* Data structures for mutex handling. The structure of the attribute type is not exposed on purpose. */ typedef union { struct __pthread_mutex_s { int __lock; unsigned int __count; int __owner; #if __WORDSIZE == 64 unsigned int __nusers; #endif /* KIND must stay at this position in the structure to maintain binary compatibility. */ int __kind; #if __WORDSIZE == 64 int __spins; __pthread_list_t __list; # define __PTHREAD_MUTEX_HAVE_PREV 1 #else unsigned int __nusers; __extension__ union { int __spins; __pthread_slist_t __list; }; #endif } __data; char __size[__SIZEOF_PTHREAD_MUTEX_T]; long int __align; } pthread_mutex_t;
接下來是如何使用互斥鎖進行互斥操做。在進行互斥操做的時候, 應該先"拿到鎖"再執行須要互斥的操做,不然可能會致使多個線程都須要訪問的數據結果不一致。 例如在一個線程在試圖修改一個變量的時候,另外一個線程也試圖去修改這個變量, 那就極可能讓後修改的這個線程把前面線程所作的修改覆蓋了。
下面是獲取鎖的操做:
pthread_mutex_lock(&mtx);
這個操做是阻塞調用的,也就是說,若是這個鎖此時正在被其它線程佔用, 那麼 pthread_mutex_lock() 調用會進入到這個鎖的排隊隊列中,並會進入阻塞狀態, 直到拿到鎖以後纔會返回。
若是不想阻塞,而是想嘗試獲取一下,若是鎖被佔用咱就不用,若是沒被佔用那就用, 這該怎麼實現呢?可使用 pthread_mutex_trylock() 函數。 這個函數和 pthread_mutex_lock() 用法同樣,只不過當請求的鎖正在被佔用的時候, 不會進入阻塞狀態,而是馬上返回,並返回一個錯誤代碼 EBUSY,意思是說, 有其它線程正在使用這個鎖。
int err = pthread_mutex_trylock(&mtx); if(0 != err) { if(EBUSY == err) { //The mutex could not be acquired because it was already locked. } }
若是不想不斷的調用 pthread_mutex_trylock() 來測試互斥鎖是否可用, 而是想阻塞調用,可是增長一個超時時間呢,那麼可使用 pthread_mutex_timedlock() 來解決, 其調用方式以下:
struct timespec abs_timeout; abs_timeout.tv_sec = time(NULL) + 1; abs_timeout.tv_nsec = 0; int err = pthread_mutex_timedlock(&mtx, &abs_timeout); if(0 != err) { if(ETIMEDOUT == err) { //The mutex could not be locked before the specified timeout expired. } }
上面代碼的意思是,阻塞等待線程鎖,可是隻等1秒鐘,一秒鐘後若是還沒拿到鎖的話, 那就返回,並返回一個錯誤代碼 ETIMEDOUT,意思是超時了。
其中 timespec 定義在頭文件 time.h 中,其定義以下
struct timespec { __time_t tv_sec; /* Seconds. */ long int tv_nsec; /* Nanoseconds. */ };
還須要注意的是,這個函數裏面的時間,是絕對時間,因此這裏用 time() 函數返回的時間增長了 1 秒。
用完了互斥鎖,必定要記得釋放,否則下一個想要得到這個鎖的線程, 就只能去等着了,若是那個線程很不幸的使用了阻塞等待,那就悲催了。
釋放互斥鎖比較簡單,使用 pthread_mutex_unlock() 便可:
pthread_mutex_unlock(&mtx);
經過 man pthread_mutex_destroy 命令能夠看到 pthread_mutex_destroy() 函數的說明, 在使用此函數銷燬一個線程鎖後,線程鎖的狀態變爲"未定義"。有的 pthread_mutex_destroy 實現方式,會使線程鎖變爲一個不可用的值。一個被銷燬的線程鎖能夠被 pthread_mutex_init() 再次初始化。對被銷燬的線程鎖進行其它操做,其結果是未定義的。
對一個處於已初始化但未鎖定狀態的線程鎖進行銷燬是安全的。儘可能避免對一個處於鎖定狀態的線程鎖進行銷燬操做。
銷燬線程鎖的操做以下:
pthread_mutex_destroy(&mtx)