互斥的概念
在多線程編程中,引入了對象互斥鎖的概念,來保證共享數據操作的完整性。 每個對象都對應於一個可稱為" 互斥鎖" 的標記,這個標記用來保證在任一時刻, 只能有一個線程訪問該對象。
互斥鎖操作
互斥鎖也可以叫線程鎖,接下來說說互斥鎖的的使用方法。
對互斥鎖進行操作的函數,常用的有如下幾個:
#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() 對互斥鎖進行初始化操作。
//第二個參數是 NULL 的話,互斥鎖的屬性會設置為默認屬性 pthread_mutex_init(&mtx, NULL);
除了使用 pthread_mutex_init() 初始化一個互斥鎖,我們還可以使用下面的方式定義一個互斥鎖:
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
在頭文件 /usr/include/pthread.h 中,對 PTHREAD_MUTEX_INITIALIZER 的聲明如下
# 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)
同步地址:https://www.fengbohello.top/archives/linux-pthread-mutex