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

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