linux線程的互斥鎖(mutex)是用于保護共享資源的同步機制,確保在多線程環境中,多個線程不會同時訪問或修改同一個資源,從而避免數據競爭或不一致的問題。

互斥鎖是一種二進制鎖,也就是說它只有兩種狀態:鎖定(locked)和解鎖(unlocked)。
當一個線程想要訪問受保護的共享資源時,它首先必須嘗試鎖定互斥鎖,如果鎖已經被其他線程持有,則它必須等待,直到鎖被釋放。
當線程完成對資源的操作后,它需要解鎖互斥鎖,以便其他線程可以訪問該資源。
互斥鎖的工作原理:
鎖定(lock):線程調用pthread_mutex_lock(),如果互斥鎖已經解鎖,則該線程成功鎖定,并進入臨界區訪問共享資源;如果鎖已被其他線程占有,則當前線程將阻塞,直到鎖被釋放。解鎖(unlock):線程完成對共享資源的操作后,調用pthread_mutex_unlock(),這會釋放鎖,其他被阻塞的線程將有機會鎖定并訪問該資源。
在Linux下,線程互斥鎖主要通過POSIX線程庫(pthread)來實現,通常的步驟包括:
初始化互斥鎖:使用pthread_mutex_init()或直接用靜態初始化PTHREAD_MUTEX_INITIALIZER。鎖定互斥鎖:在線程需要訪問共享資源前,使用pthread_mutex_lock()鎖定。訪問共享資源:執行需要對共享資源的操作。解鎖互斥鎖:訪問結束后,使用pthread_mutex_unlock()解鎖。銷毀互斥鎖:使用pthread_mutex_destroy()銷毀互斥鎖,通常在不再使用該互斥鎖時進行。
1、互斥鎖的初始化
互斥鎖在使用之前必須先進行初始化操作。
可以通過兩種方式來初始化互斥鎖:靜態初始化和動態初始化。
1.1、靜態初始化
靜態初始化使用 PTHREAD_MUTEX_INITIALIZER 宏來初始化互斥鎖,這是一種常見且簡便的初始化方法。
無需顯式調用初始化函數,適用于全局互斥鎖。
代碼語言:JavaScript代碼運行次數:0運行復制
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
在這種方式下,互斥鎖被設置為默認屬性。靜態初始化不需要任何額外參數,并且返回值是隱式的,不會返回錯誤碼。
1.2、動態初始化
動態初始化通過 pthread_mutex_init() 函數完成,適用于需要在運行時動態分配的互斥鎖,或需要自定義互斥鎖屬性的情況。
代碼語言:javascript代碼運行次數:0運行復制
pthread_mutex_t mutex;pthread_mutexattr_t attr;pthread_mutexattr_init(&attr); // 初始化互斥鎖屬性// 初始化互斥鎖,第二個參數為屬性,如果不需要自定義屬性可以傳入 NULLint ret = pthread_mutex_init(&mutex, &attr);if (ret != 0) { // 處理初始化失敗}pthread_mutexattr_destroy(&attr); // 銷毀屬性
參數:
mutex:指向 pthread_mutex_t 類型互斥鎖的指針。attr:互斥鎖的屬性指針,可以設置互斥鎖的行為。如果不需要自定義屬性,傳入 NULL 表示使用默認屬性。
返回值:成功時返回 0,失敗時返回非零錯誤碼。常見錯誤碼包括:
EINVAL:attr 屬性無效。EBUSY:互斥鎖已經被初始化。ENOMEM:內存不足,無法分配資源。
2、互斥鎖加鎖與解鎖
2.1、互斥鎖加鎖
pthread_mutex_lock() 用于對互斥鎖加鎖。
如果互斥鎖已經被其他線程鎖住,調用線程將進入阻塞狀態,直到該互斥鎖被解鎖。
代碼語言:javascript代碼運行次數:0運行復制
pthread_mutex_lock(&mutex); // 加鎖
參數:mutex 是指向要加鎖的 pthread_mutex_t 互斥鎖對象的指針。
返回值:成功時返回 0。如果出現錯誤,返回非零錯誤碼:
EINVAL:互斥鎖無效。EDEADLK:線程試圖遞歸加鎖一個非遞歸互斥鎖(導致死鎖)。2.2、互斥鎖解鎖
pthread_mutex_unlock() 用于解鎖已經加鎖的互斥鎖。
如果其他線程正等待此互斥鎖,它將被喚醒并獲取鎖。
代碼語言:javascript代碼運行次數:0運行復制
pthread_mutex_unlock(&mutex); // 解鎖
參數:mutex 是指向要解鎖的 pthread_mutex_t 互斥鎖對象的指針。
返回值:成功時返回 0。可能的錯誤碼:
EINVAL:互斥鎖無效。EPERM:當前線程沒有持有該互斥鎖。
3、非阻塞加鎖
pthread_mutex_trylock() 是一種非阻塞加鎖操作。
如果互斥鎖已經被其他線程鎖住,它不會阻塞,而是立即返回錯誤碼 EBUSY。
代碼語言:javascript代碼運行次數:0運行復制
int try_lock_example() { int ret = pthread_mutex_trylock(&mutex); if (ret == 0) { // 鎖定成功 printf("鎖定成功!n"); pthread_mutex_unlock(&mutex); // 解鎖 } else if (ret == EBUSY) { // 鎖定失敗,互斥鎖已被其他線程持有 printf("鎖定失敗,互斥鎖被占用。n"); } else { // 其他錯誤 printf("嘗試鎖定時出現錯誤。n"); } return 0;}
參數:mutex 是指向要加鎖的 pthread_mutex_t 互斥鎖對象的指針。
返回值:
0:成功加鎖。EBUSY:互斥鎖已經被其他線程持有,無法加鎖。EINVAL:互斥鎖無效。
4、銷毀互斥鎖
使用完互斥鎖后,應該通過 pthread_mutex_destroy() 釋放與之相關的資源。
銷毀互斥鎖之前,確保它已經被解鎖。
代碼語言:javascript代碼運行次數:0運行復制
pthread_mutex_destroy(&mutex);
參數:mutex 是指向要銷毀的 pthread_mutex_t 互斥鎖對象的指針。
返回值:
0:成功銷毀。EINVAL:互斥鎖無效或未被初始化。EBUSY:互斥鎖仍被鎖定,不能銷毀。
銷毀互斥鎖后,它不能再被使用,除非重新初始化。
5、互斥鎖死鎖問題
如果一個線程在鎖定互斥鎖后由于某種原因沒有解鎖(如忘記調用pthread_mutex_unlock()或在臨界區中發生異常終止),其他線程將永遠無法獲得該鎖,導致系統卡住。
以下例子中,線程 A 鎖定 mutex1,線程 B 鎖定 mutex2,接著 A 和 B 分別嘗試鎖定對方已經持有的互斥鎖,導致相互等待,程序進入死鎖狀態。
代碼語言:javascript代碼運行次數:0運行復制
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;void *threadA(void *arg) { pthread_mutex_lock(&mutex1); sleep(1); // 模擬工作 pthread_mutex_lock(&mutex2); // 這里會發生死鎖 pthread_mutex_unlock(&mutex2); pthread_mutex_unlock(&mutex1); return NULL;}void *threadB(void *arg) { pthread_mutex_lock(&mutex2); sleep(1); // 模擬工作 pthread_mutex_lock(&mutex1); // 這里會發生死鎖 pthread_mutex_unlock(&mutex1); pthread_mutex_unlock(&mutex2); return NULL;}
預防死鎖方法:
固定鎖順序:所有線程在請求多個鎖時,必須按照相同的順序來請求。超時加鎖:使用 pthread_mutex_trylock(),可以避免線程長時間等待鎖。
6、互斥鎖的屬性
pthread_mutexattr_t 結構體用于控制互斥鎖的行為。常用屬性包括互斥鎖的類型。
通過 pthread_mutexattr_settype() 設置互斥鎖的類型。
常見類型包括:
PTHREAD_MUTEX_NORMAL:普通互斥鎖,不會檢查錯誤,遞歸加鎖會導致死鎖。PTHREAD_MUTEX_ERRORCHECK:錯誤檢查互斥鎖,如果同一線程重復加鎖,返回 EDEADLK 錯誤。PTHREAD_MUTEX_RECURSIVE:遞歸鎖,允許同一線程對互斥鎖多次加鎖,但需要相同次數的解鎖。PTHREAD_MUTEX_DEFAULT:默認行為,通常與 PTHREAD_MUTEX_NORMAL 等價。
設置遞歸鎖的示例:
代碼語言:javascript代碼運行次數:0運行復制
pthread_mutex_t mutex;pthread_mutexattr_t attr;pthread_mutexattr_init(&attr); // 初始化屬性pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 設置遞歸鎖類型pthread_mutex_init(&mutex, &attr); // 初始化互斥鎖pthread_mutexattr_destroy(&attr); // 銷毀屬性
返回值:
0:成功。EINVAL:互斥鎖屬性無效。
互斥鎖的正確使用包括初始化、加鎖、解鎖和銷毀。
通過靜態或動態方法初始化互斥鎖,根據需求選擇合適的鎖類型,可以有效避免線程競爭和死鎖問題。