Linux多線程同步機制-互斥鎖(mutex)
引言
在Linux多線程編程中,互斥鎖(Mutex)是一種非常重要的同步機制,用于控制對共享資源的訪問,確保在任意時刻只有一個線程可以訪問特定的資源或代碼段,即臨界區(qū)。互斥鎖的主要用途是防止多個線程同時訪問共享資源,從而避免競爭條件和數(shù)據(jù)不一致的問題。
互斥鎖的工作原理相對簡單,它通過鎖定和解鎖操作來控制對共享資源的訪問。當一個線程需要訪問共享資源時,它首先嘗試鎖定互斥鎖。如果互斥鎖已經(jīng)被其他線程鎖定,請求線程將被阻塞,直到互斥鎖被解鎖?;コ怄i的鎖定和解鎖操作必須是成對出現(xiàn)的,以確保對共享資源的正確訪問。
一、互斥鎖原型
在 Linux 中,互斥鎖通常通過 POSIX 線程庫(pthread)來實現(xiàn)。pthread 庫提供了一系列的函數(shù)來創(chuàng)建、初始化、鎖定、解鎖和銷毀互斥鎖,如pthread_mutex_init()、pthread_mutex_lock()、pthread_mutex_unlock()和pthread_mutex_destroy()等?;コ怄i的初始化是通過pthread_mutex_init()函數(shù)完成的,該函數(shù)會分配必要的資源來創(chuàng)建一個互斥鎖,并將其初始化為未鎖定狀態(tài)。
pthread_mutex_t 是 POSIX 線程庫中用于互斥鎖的數(shù)據(jù)類型。以下是一些與 pthread_mutex_t 相關的函數(shù)原型:
初始化互斥鎖:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
mutex:指向 pthread_mutex_t 結構的指針,用于初始化互斥鎖。
attr:指向 pthread_mutexattr_t 結構的指針,包含互斥鎖的屬性。如果為 NULL,則使用默認屬性。
銷毀互斥鎖:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- mutex:指向已經(jīng)初始化的 pthread_mutex_t 結構的指針。銷毀互斥鎖前,確保互斥鎖沒有被鎖定。
鎖定互斥鎖:
int pthread_mutex_lock(pthread_mutex_t *mutex);
- mutex:指向已經(jīng)初始化的 pthread_mutex_t 結構的指針。如果互斥鎖已經(jīng)被其他線程鎖定,則調(diào)用線程將被阻塞,直到互斥鎖被解鎖。
嘗試鎖定互斥鎖(非阻塞):
int pthread_mutex_trylock(pthread_mutex_t *mutex);
- mutex:指向已經(jīng)初始化的 pthread_mutex_t 結構的指針。如果互斥鎖已經(jīng)被鎖定,則立即返回錯誤碼 EBUSY,而不是等待互斥鎖變?yōu)榭捎谩?/li>
定時鎖定互斥鎖(可指定超時時間):
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
- mutex:指向已經(jīng)初始化的 pthread_mutex_t 結構的指針。
- abstime:指向 timespec 結構的指針,包含超時時間。如果超時時間到達,互斥鎖仍未解鎖,則返回 ETIMEDOUT。
解鎖互斥鎖:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- mutex:指向已經(jīng)初始化且當前被調(diào)用線程鎖定的 pthread_mutex_t 結構的指針。調(diào)用此函數(shù)將釋放互斥鎖,允許其他線程鎖定它。
注意:互斥鎖的初始化方式主要有兩種:靜態(tài)初始化和動態(tài)初始化。
- 靜態(tài)初始化: 使用宏 PTHREAD_MUTEX_INITIALIZER 可以在聲明互斥鎖變量時直接初始化。這種方式是編譯時初始化,無需調(diào)用初始化函數(shù)。例如:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
這種方式適用于靜態(tài)分配的互斥鎖,即在程序的整個生命周期內(nèi)都存在的鎖。
- 動態(tài)初始化: 使用 pthread_mutex_init() 函數(shù)可以在程序運行時初始化互斥鎖。這種方式需要顯式調(diào)用函數(shù)進行初始化和銷毀,適用于需要動態(tài)創(chuàng)建和銷毀的互斥鎖。例如:
pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t));pthread_mutex_init(mutex, NULL); // NULL 表示使用默認屬性
使用動態(tài)初始化時,需要在不再需要互斥鎖時調(diào)用 pthread_mutex_destroy() 來銷毀它,并釋放分配的內(nèi)存。
互斥鎖的屬性可以在初始化時指定,如果不設置屬性(即使用 NULL 或默認屬性),則使用系統(tǒng)默認的互斥鎖屬性。在Linux系統(tǒng)中,屬性可以用來定義不同類型的互斥鎖,例如:
- 普通互斥鎖(PTHREAD_MUTEX_TIMED_NP)
- 遞歸互斥鎖(PTHREAD_MUTEX_RECURSIVE_NP)
- 錯誤檢查互斥鎖(PTHREAD_MUTEX_ERRORCHECK_NP)
- 適應性互斥鎖(PTHREAD_MUTEX_ADAPTIVE_NP)
正確初始化互斥鎖對于避免潛在的同步問題是非常重要的。使用靜態(tài)初始化可以簡化代碼并減少出錯機會,而動態(tài)初始化提供了更大的靈活性。在實際編程中,應根據(jù)具體需求選擇適合的初始化方式。
這些函數(shù)是 POSIX 線程庫中用于線程同步的基礎,通過它們可以安全地在多線程程序中控制對共享資源的訪問。
三、互斥鎖特性
互斥鎖是一種簡單的加鎖的方法來控制對共享資源的訪問,互斥鎖只有兩種狀態(tài),即上鎖( lock )和解鎖(unlock),如果互斥量已經(jīng)上鎖,調(diào)用線程會阻塞,直到互斥量被解鎖。在完成了對共享資源的訪問后,要對互斥量進行解鎖。
互斥鎖特點如下:
- 原子性:把一個互斥量鎖定為一個原子操作,操作系統(tǒng)保證了如果一個線程鎖定了一個互斥量,沒有其他線程在同一時間可以再成功鎖定這個互斥量;
- 唯一性:如果一個線程鎖定了一個互斥量,在它解除鎖定之前,沒有其他線程可以鎖定這個互斥量;
- 非忙等待:如果一個線程已經(jīng)鎖定了一個互斥量,第二個線程又試圖去鎖定這個互斥量,則第二個線程將被掛起(不占用任何 cpu 資源),直到第一個線程解除對這個互斥量的鎖定為止,第二個線程則被喚醒并繼續(xù)執(zhí)行,同時鎖定這個互斥量。
四、互斥鎖加鎖阻塞
互斥鎖是一種【獨占鎖】,比如當線程 A 加鎖成功后,此時互斥鎖已經(jīng)被線程 A 獨占了,只要線程 A 沒有釋放手中的鎖,線程 B 加鎖就會失敗,于是就會釋放 CPU 讓給其他線程,既然線程 B 釋放掉了 CPU,自然線程 B 加鎖的代碼就會被阻塞。
對于互斥鎖加鎖失敗而阻塞的現(xiàn)象,是由操作系統(tǒng)內(nèi)核實現(xiàn)的。當加鎖失敗時,內(nèi)核會將線程置為【睡眠】狀態(tài),等到鎖被釋放后,內(nèi)核會在合適的時機喚醒線程,當這個線程成功獲取到鎖后,于是就可以繼續(xù)執(zhí)行。如下圖:
圖片
所以,互斥鎖加鎖失敗時,會從用戶態(tài)陷入到內(nèi)核態(tài),讓內(nèi)核幫我們切換線程,雖然簡化了使用鎖的難度,但是存在一定的性能開銷成本。
那這個開銷成本是什么呢?會有兩次線程上下文切換的成本:
- 當線程加鎖失敗時,內(nèi)核會把線程的狀態(tài)從【運行】狀態(tài)設置為【睡眠】狀態(tài),然后把 CPU 切換給其他線程運行;
- 接著,當鎖被釋放時,之前【睡眠】狀態(tài)的線程會變?yōu)椤揪途w】狀態(tài),然后內(nèi)核會在合適的時間,把 CPU 切換給該線程運行。
線程的上下文切換的是什么?當兩個線程是屬于同一個進程,因為虛擬內(nèi)存是共享的,所以在切換時,虛擬內(nèi)存這些資源就保持不動,只需要切換線程的私有數(shù)據(jù)、寄存器等不共享的數(shù)據(jù)。
上下切換的耗時有大佬統(tǒng)計過,大概在幾十納秒到幾微秒之間,如果你鎖住的代碼執(zhí)行時間比較短,那可能上下文切換的時間都比你鎖住的代碼執(zhí)行時間還要長。
所以,如果你能確定被鎖住的代碼執(zhí)行時間很短,就不應該用互斥鎖,而應該選用自旋鎖,否則使用互斥鎖。
五、互斥鎖死鎖
互斥鎖導致的死鎖是多線程編程中的一個常見問題。死鎖發(fā)生時,兩個或多個線程被無限期地阻塞,因為它們在等待對方釋放鎖。以下是一些關于互斥鎖死鎖的信息:
- 死鎖的條件:死鎖通常發(fā)生在以下四個條件同時滿足時:
互斥:資源不能被多個線程同時訪問。
占有和等待:一個線程持有至少一個鎖,并且等待獲取其他線程持有的鎖。
不可剝奪:已經(jīng)獲得的鎖不能被其他線程強行剝奪,只能由持有它的線程釋放。
循環(huán)等待:存在一個線程持有鎖的循環(huán)鏈,每個線程都在等待下一個線程持有的鎖。
- 死鎖的避免:可以通過以下幾種策略來避免死鎖:
固定順序加鎖:總是以相同的順序獲取多個鎖。
超時嘗試加鎖:使用 pthread_mutex_trylock() 或其他帶有超時功能的加鎖方法。
一次性獲取所有鎖:在開始訪問共享資源前,先獲取所有需要的鎖。
使用死鎖檢測算法:定期檢測死鎖情況,并采取措施解決。
- 死鎖的解除:如果檢測到死鎖,可以采取以下措施:
剝奪資源:從其他線程剝奪資源,賦予死鎖線程。
撤銷進程:終止一個或多個線程或進程,打破死鎖狀態(tài)。
資源重分配:重新分配資源,以打破循環(huán)等待條件。
- 避免嵌套鎖:盡量減少鎖的嵌套使用,如果必須嵌套使用,確保內(nèi)層鎖總是不同類型的或者使用不同的加鎖順序。
- 鎖的分級管理:將鎖分級,高級別的鎖可以包含多個低級別的鎖,確保在請求高級別鎖時,已經(jīng)持有所有需要的低級別鎖。
通過采取這些措施,可以降低死鎖發(fā)生的風險,并提高多線程程序的穩(wěn)定性和可靠性。
六、互斥鎖實戰(zhàn)
1.互斥鎖加解鎖
以下是一個簡單的示例代碼,展示了如何在多線程環(huán)境中使用 pthread_mutex_t 來同步對同一個文件的讀寫操作:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREADS 5 // 定義宏來設置線程數(shù)量
pthread_mutex_t file_mutex = PTHREAD_MUTEX_INITIALIZER; // 全局互斥鎖
const char *data_to_write = "Thread data\n";
void perform_file_write(const char *filename) {
FILE *fp = fopen(filename, "a");
if (fp == NULL) {
perror("Error opening file for write");
return;
}
pthread_mutex_lock(&file_mutex); // 加鎖
fputs(data_to_write, fp);
pthread_mutex_unlock(&file_mutex); // 解鎖
fclose(fp);
}
void perform_file_read(const char *filename) {
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
perror("Error opening file for read");
return;
}
char buffer[256];
pthread_mutex_lock(&file_mutex); // 加鎖
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer); // 打印讀取的數(shù)據(jù)
}
pthread_mutex_unlock(&file_mutex); // 解鎖
fclose(fp);
}
void *thread_function(void *arg) {
int is_write = *(int *)arg; // 根據(jù)傳入的參數(shù)決定操作類型
const char *filename = "example.txt"; // 要操作的文件名
if (is_write) {
perform_file_write(filename);
} else {
perform_file_read(filename);
}
return NULL;
}
int main() {
int *args = malloc(NUM_THREADS * sizeof(int)); // 動態(tài)分配參數(shù)數(shù)組
if (args == NULL) {
perror("Failed to allocate memory for args");
return 1;
}
pthread_t threads[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; ++i) {
args[i] = (i == 0); // 第一個線程執(zhí)行寫操作,其余執(zhí)行讀操作
if (pthread_create(&threads[i], NULL, thread_function, &args[i]) != 0) {
perror("Failed to create thread");
free(args);
return 1;
}
}
for (int i = 0; i < NUM_THREADS; ++i) {
pthread_join(threads[i], NULL);
}
free(args); // 釋放參數(shù)數(shù)組
// 靜態(tài)初始化的互斥鎖不需要顯式銷毀
return 0;
}
在這個示例中,創(chuàng)建了一個互斥鎖 file_mutex 來同步對文件 example.txt 的訪問。使用 PTHREAD_MUTEX_INITIALIZER 直接初始化互斥鎖,無需再調(diào)用pthread_mutex_init 函數(shù)了。我們定義了 perform_file_read 和 perform_file_write 函數(shù)來執(zhí)行實際的文件讀寫操作,并在這些操作前后使用 pthread_mutex_lock 和 pthread_mutex_unlock 來確保每次只有一個線程可以訪問文件。在 main 函數(shù)中,創(chuàng)建了一個線程數(shù)組,并設置第一個線程執(zhí)行寫操作,其余線程執(zhí)行讀操作。使用 perror 來打印出創(chuàng)建線程或文件操作失敗時的錯誤信息。最后,我們在主函數(shù)中等待所有線程完成,并且由于使用了靜態(tài)初始化互斥鎖,我們不需要調(diào)用 pthread_mutex_destroy 來銷毀互斥鎖。
程序運行結果如下:
[root@localhost multi_pthread_file]# ./multi_pthread_file_rw
Thread data
Thread data
Thread data
Thread data
[root@localhost multi_pthread_file]# cat example.txt
Thread data
通過程序運行結果可知,我們通過給寫操作和讀操作加互斥鎖,成功實現(xiàn)我們預期的目標,即寫一次數(shù)據(jù),讀四次數(shù)據(jù)。
2.互斥鎖死鎖
以下是一個 C 語言中可能導致死鎖的互斥鎖代碼示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex1;
pthread_mutex_t mutex2;
void *thread1Function(void *arg) {
pthread_mutex_lock(&mutex1);
printf("Thread 1 acquired mutex1\n");
sleep(1);
pthread_mutex_lock(&mutex2);
printf("Thread 1 acquired mutex2\n");
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
return NULL;
}
void *thread2Function(void *arg) {
pthread_mutex_lock(&mutex2);
printf("Thread 2 acquired mutex2\n");
sleep(1);
pthread_mutex_lock(&mutex1);
printf("Thread 2 acquired mutex1\n");
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex2);
return NULL;
}
int main() {
pthread_t thread1, thread2;
pthread_mutex_init(&mutex1, NULL);
pthread_mutex_init(&mutex2, NULL);
pthread_create(&thread1, NULL, thread1Function, NULL);
pthread_create(&thread2, NULL, thread2Function, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&mutex1);
pthread_mutex_destroy(&mutex2);
return 0;
}
在這個示例中,thread1 先獲取 mutex1 然后嘗試獲取 mutex2,而 thread2 先獲取 mutex2 然后嘗試獲取 mutex1,可能會導致兩個線程相互等待對方釋放鎖,從而造成死鎖。
【拓展】上文中 sleep(1) 的作用:
在上述代碼中,兩個線程函數(shù)中設置 sleep(1) 的主要目的是增加死鎖發(fā)生的可能性。當線程獲取一個互斥鎖后,通過 sleep(1) 讓線程暫停一段時間,使得另一個線程有機會去獲取另一個互斥鎖,從而更有可能形成兩個線程相互等待對方持有的鎖的情況,導致死鎖的發(fā)生。
如果沒有這個 sleep(1) ,由于線程執(zhí)行速度非???,可能在一個線程完成對兩個鎖的獲取和操作之前,另一個線程還沒有機會執(zhí)行獲取鎖的操作,這樣死鎖就不太容易出現(xiàn),不利于演示和觀察死鎖的情況。
通過添加 sleep(1) ,模擬了線程操作中的一定延遲,使得線程之間的競爭和等待更加明顯,更有可能展示出死鎖的現(xiàn)象。
編譯并執(zhí)行可執(zhí)行程序如下:
[root@localhost multi_pthread_file]# ./dead_lock
Thread 1 acquired mutex1
Thread 2 acquired mutex2
執(zhí)行可執(zhí)行程序 dead_lock ,發(fā)現(xiàn)該進程在輸出兩條信息后卡住不動,無法繼續(xù)執(zhí)行,出現(xiàn)停滯或長時間無響應的情況,由此推斷該進程死鎖了。
使用 gdb 命令附加到可能發(fā)生死鎖的進程。可以通過 ps 命令找到進程 ID(PID),然后使用 gdb 加上進程 ID 來附加到該進程。例如:
[root@localhost multi_pthread_file]# gdb -p 251640 -q
Attaching to process 251640
[New LWP 251641]
[New LWP 251642]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
0x00007fb09e6906cd in __pthread_timedjoin_ex () from /lib64/libpthread.so.0
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-251.el8.x86_64
(gdb) info threads
Id Target Id Frame
* 1 Thread 0x7fb09eac8740 (LWP 251640) "dead_lock" 0x00007fb09e6906cd in __pthread_timedjoin_ex () from /lib64/libpthread.so.0
2 Thread 0x7fb09e2b0700 (LWP 251641) "dead_lock" 0x00007fb09e69885d in __lll_lock_wait () from /lib64/libpthread.so.0
3 Thread 0x7fb09daaf700 (LWP 251642) "dead_lock" 0x00007fb09e69885d in __lll_lock_wait () from /lib64/libpthread.so.0
(gdb)
使用 info threads 命令列出進程中的所有線程。這將顯示每個線程的 ID 和當前狀態(tài)。死鎖的線程通常會顯示為在等待鎖(例如,在 __lll_lock_wait)。根據(jù)上述結果可知,該進程已死鎖。
3互斥鎖死鎖檢測和恢復
以下是一個使用 C 語言實現(xiàn)互斥鎖、死鎖檢測和恢復的簡單示例代碼:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;
int lock1_held = 0;
int lock2_held = 0;
void *thread1(void *arg) {
pthread_mutex_lock(&lock1);
printf("Thread 1 acquired lock 1\n");
lock1_held = 1;
sleep(1); // 模擬耗時操作
while (lock2_held && lock1_held) { // 持續(xù)檢測死鎖條件
printf("Thread 1 detects potential deadlock and releases lock 1\n");
pthread_mutex_unlock(&lock1);
lock1_held = 0;
sleep(1); // 等待一段時間后再次嘗試
pthread_mutex_lock(&lock1);
lock1_held = 1;
}
pthread_mutex_lock(&lock2);
printf("Thread 1 acquired lock 2\n");
pthread_mutex_unlock(&lock2);
pthread_mutex_unlock(&lock1);
lock1_held = 0;
lock2_held = 0;
return NULL;
}
void *thread2(void *arg) {
pthread_mutex_lock(&lock2);
printf("Thread 2 acquired lock 2\n");
lock2_held = 1;
sleep(1); // 模擬耗時操作
while (lock1_held && lock2_held) { // 持續(xù)檢測死鎖條件
printf("Thread 2 detects potential deadlock and releases lock 2\n");
pthread_mutex_unlock(&lock2);
lock2_held = 0;
sleep(1); // 等待一段時間后再次嘗試
pthread_mutex_lock(&lock2);
lock2_held = 1;
}
pthread_mutex_lock(&lock1);
printf("Thread 2 acquired lock 1\n");
pthread_mutex_unlock(&lock1);
pthread_mutex_unlock(&lock2);
lock1_held = 0;
lock2_held = 0;
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, thread1, NULL);
pthread_create(&t2, NULL, thread2, NULL);
sleep(5); // 等待一段時間,讓死鎖有機會發(fā)生
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
在上述示例中,我們進行了如下操作:
1)定義了兩個互斥鎖 lock1 和 lock2,以及兩個標志 lock1_held 和 lock2_held 來跟蹤鎖的持有狀態(tài)。
2)thread1 函數(shù):
- 首先獲取 lock1 并設置相應的標志。
- 經(jīng)過一段模擬耗時操作后,進入一個 while 循環(huán),持續(xù)檢測是否同時持有 lock1 和 lock2 導致死鎖。
- 如果檢測到死鎖,打印提示信息,釋放 lock1,將標志重置,等待一段時間后重新獲取 lock1 并再次設置標志。
- 如果沒有死鎖,獲取 lock2,完成操作后釋放兩個鎖并重置標志。
3)thread2 函數(shù):
- 與 thread1 函數(shù)類似,首先獲取 lock2 并設置標志。
- 經(jīng)過模擬耗時操作后,在 while 循環(huán)中檢測死鎖情況并進行相應處理。
- 最終獲取 lock1,完成操作后釋放鎖和重置標志。
4)main 函數(shù):
- 創(chuàng)建兩個線程分別執(zhí)行 thread1 和 thread2 函數(shù)。
- 等待一段時間,讓死鎖有機會發(fā)生。
- 等待兩個線程結束。
[root@localhost multi_pthread_file]# ./dead_lock_detect
Thread 1 acquired lock 1
Thread 2 acquired lock 2
Thread 1 detects potential deadlock and releases lock 1
Thread 2 detects potential deadlock and releases lock 2
Thread 1 acquired lock 2
Thread 2 acquired lock 1
通過這種方式,每個線程在獲取第二個鎖之前,持續(xù)檢測死鎖情況,并在可能死鎖時采取釋放已持有的鎖、等待后重新嘗試獲取的策略,以盡量避免死鎖的發(fā)生。但需要注意的是,這仍然不是一種完全可靠的死鎖避免機制,在復雜的多線程環(huán)境中,可能需要更完善的同步和協(xié)調(diào)策略。
4.固定順序加鎖避免死鎖
以下是一個使用 C 語言實現(xiàn)互斥鎖通過固定加鎖順序來避免死鎖的發(fā)生簡單示例代碼:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 互斥鎖
pthread_mutex_t mutex1;
pthread_mutex_t mutex2;
// 死鎖檢測標志
int deadlockDetected = 0;
void *thread1Function(void *arg) {
pthread_mutex_lock(&mutex1);
printf("Thread 1 acquired mutex1\n");
sleep(1);
pthread_mutex_lock(&mutex2);
printf("Thread 1 acquired mutex2\n");
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
return NULL;
}
void *thread2Function(void *arg) {
pthread_mutex_lock(&mutex1);
printf("Thread 2 acquired mutex1\n");
sleep(1);
pthread_mutex_lock(&mutex2);
printf("Thread 2 acquired mutex2\n");
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
return NULL;
}
// 模擬死鎖檢測
void detectDeadlock() {
sleep(3);
// 由于固定了加鎖順序,這里不會發(fā)生死鎖,無需檢測
}
int main() {
pthread_t thread1, thread2;
pthread_mutex_init(&mutex1, NULL);
pthread_mutex_init(&mutex2, NULL);
pthread_create(&thread1, NULL, thread1Function, NULL);
pthread_create(&thread2, NULL, thread2Function, NULL);
detectDeadlock();
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&mutex1);
pthread_mutex_destroy(&mutex2);
return 0;
}
在上述示例中,我們進行了如下操作:
- 首先,定義了兩個互斥鎖 mutex1 和 mutex2,以及一個標志 deadlockDetected 用于死鎖檢測(但在當前代碼中未實際使用)。
- thread1Function 和 thread2Function 分別是兩個線程的執(zhí)行函數(shù)。
thread1Function 先獲取 mutex1,等待 1 秒后再獲取 mutex2,最后釋放兩個鎖。
thread2Function 邏輯相同,也是先獲取 mutex1,等待 1 秒后獲取 mutex2,最后釋放。
- detectDeadlock 函數(shù)用于模擬死鎖檢測,但由于當前固定了加鎖順序,實際上不會發(fā)生死鎖,所以此函數(shù)內(nèi)未進行真正的檢測操作。
- 在 main 函數(shù)中:
初始化兩個互斥鎖。
創(chuàng)建兩個線程分別執(zhí)行 thread1Function 和 thread2Function 。
調(diào)用 detectDeadlock 函數(shù)進行死鎖檢測(但如前所述,此處在當前代碼中未實際生效)。
使用 pthread_join 等待兩個線程結束。
最后銷毀兩個互斥鎖。 總的來說,這段代碼創(chuàng)建了兩個線程并嘗試獲取兩個互斥鎖,但由于固定的加鎖順序,不會產(chǎn)生死鎖。
程序運行結果如下:
[root@localhost multi_pthread_file]# ./dead_lock_detect_fixed_mutex
Thread 1 acquired mutex1
Thread 1 acquired mutex2
Thread 2 acquired mutex1
Thread 2 acquired mutex2
通過程序運行結果可知,多個線程使用固定的加鎖順序,不會產(chǎn)生死鎖。
5.pthread_mutex_trylock()函數(shù)避免死鎖
以下是一個使用 C 語言實現(xiàn)互斥鎖通過 pthread_mutex_trylock() 來避免死鎖的發(fā)生簡單示例代碼:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
pthread_mutex_t lock1, lock2;
void *thread1(void *arg) {
while (1) {
// 循環(huán)嘗試獲取鎖 1
if (pthread_mutex_trylock(&lock1) == 0) {
printf("Thread 1: Acquired lock 1\n");
break;
}
printf("Thread 1: Failed to acquire lock 1, retrying...\n");
sleep(1); // 等待一段時間再重試
}
// 模擬一些操作
sleep(1);
while (1) {
// 循環(huán)嘗試獲取鎖 2
if (pthread_mutex_trylock(&lock2) == 0) {
printf("Thread 1: Acquired lock 2\n");
break;
}
printf("Thread 1: Failed to acquire lock 2, retrying...\n");
pthread_mutex_unlock(&lock1); // 釋放鎖 1
sleep(1); // 等待一段時間再重試
}
// 釋放鎖 2 和鎖 1
pthread_mutex_unlock(&lock2);
pthread_mutex_unlock(&lock1);
return NULL;
}
void *thread2(void *arg) {
while (1) {
// 循環(huán)嘗試獲取鎖 2
if (pthread_mutex_trylock(&lock2) == 0) {
printf("Thread 2: Acquired lock 2\n");
break;
}
printf("Thread 2: Failed to acquire lock 2, retrying...\n");
sleep(1); // 等待一段時間再重試
}
// 模擬一些操作
sleep(1);
while (1) {
// 循環(huán)嘗試獲取鎖 1
if (pthread_mutex_trylock(&lock1) == 0) {
printf("Thread 2: Acquired lock 1\n");
break;
}
printf("Thread 2: Failed to acquire lock 1, retrying...\n");
pthread_mutex_unlock(&lock2); // 釋放鎖 2
sleep(1); // 等待一段時間再重試
}
// 釋放鎖 1 和鎖 2
pthread_mutex_unlock(&lock1);
pthread_mutex_unlock(&lock2);
return NULL;
}
int main() {
pthread_t t1, t2;
// 初始化鎖
pthread_mutex_init(&lock1, NULL);
pthread_mutex_init(&lock2, NULL);
// 創(chuàng)建線程
pthread_create(&t1, NULL, thread1, NULL);
pthread_create(&t2, NULL, thread2, NULL);
// 等待線程結束
pthread_join(t1, NULL);
pthread_join(t2, NULL);
// 銷毀鎖
pthread_mutex_destroy(&lock1);
pthread_mutex_destroy(&lock2);
return 0;
}
在上述示例中,我們進行了如下操作:
- 首先,在程序中定義了兩個互斥鎖 lock1 和 lock2,用于控制線程對資源的訪問。
- thread1 函數(shù):
它通過一個無休止的 while 循環(huán)來不斷嘗試獲取 lock1 鎖。
一旦成功獲取,會輸出相應的成功獲取信息,并立即跳出當前的循環(huán)。
倘若獲取失敗,會輸出失敗提示,并等待 1 秒鐘,然后繼續(xù)下一輪的獲取嘗試。
在成功獲取 lock1 之后,經(jīng)過 1 秒鐘的模擬操作,再次進入另一個 while 循環(huán),嘗試獲取 lock2 。
若獲取 lock2 失敗,會輸出失敗信息,同時釋放之前已獲取到的 lock1 ,接著等待 1 秒鐘,再次進行獲取嘗試。
當成功獲取到 lock2 后,按順序釋放 lock2 和 lock1 ,完成線程的操作。
- thread2 函數(shù):
其邏輯與 thread1 相似。首先通過無限的 while 循環(huán)嘗試獲取 lock2 。
成功獲取時輸出信息并跳出循環(huán),失敗則輸出提示、等待 1 秒后重新嘗試。
在成功獲取 lock2 并經(jīng)過 1 秒模擬操作后,進入新的循環(huán)嘗試獲取 lock1 。
若獲取 lock1 失敗,同樣輸出失敗提示,釋放 lock2 ,等待 1 秒后重試。
最終成功獲取并按序釋放 lock1 和 lock2 。
- main 函數(shù):
聲明了兩個線程變量 t1 和 t2 。
對兩個互斥鎖進行初始化操作。
分別創(chuàng)建兩個線程,讓它們執(zhí)行 thread1 和 thread2 函數(shù)。
使用 pthread_join 函數(shù)等待這兩個線程完成執(zhí)行。
最后,銷毀兩個互斥鎖,釋放相關資源。
這種通過循環(huán)嘗試獲取鎖的方式,能夠一定程度上應對獲取鎖失敗的情況,避免線程因單次獲取失敗而直接終止或出現(xiàn)錯誤,增強了程序的穩(wěn)定性和適應性。但需要注意,這種頻繁的重試可能會帶來一定的系統(tǒng)開銷,所以在實際運用中,要根據(jù)具體場景合理設定等待時間和重試策略,以達到性能和可靠性的平衡。
程序運行結果如下:
[root@localhost multi_pthread_file]# ./dead_lock_avoid
Thread 1: Acquired lock 1
Thread 2: Acquired lock 2
Thread 1: Failed to acquire lock 2, retrying...
Thread 2: Failed to acquire lock 1, retrying...
Thread 1: Acquired lock 2
Thread 2: Acquired lock 1
通過程序運行結果可知,多個線程使用 pthread_mutex_trylock 加鎖,可以避免死鎖。
七、總結
互斥鎖(Mutex,Mutual Exclusion Lock)是一種用于多線程環(huán)境中的同步機制,具有以下關鍵特點和用途:
- 資源保護:確保在同一時刻只有一個線程能夠訪問被保護的共享資源,防止數(shù)據(jù)競爭和不一致性。
- 原子操作:保證對共享資源的操作是原子性的,即要么完全執(zhí)行,要么完全不執(zhí)行,避免中間狀態(tài)被其他線程觀察到。
- 同步協(xié)調(diào):使多個線程能夠按照預定的順序和條件進行協(xié)作,避免混亂和錯誤。
- 互斥性:當一個線程獲取互斥鎖后,其他線程在該鎖被釋放之前無法獲取,從而實現(xiàn)對關鍵代碼段或資源的獨占訪問。
在使用互斥鎖時,需要注意正確的加鎖和解鎖順序,避免死鎖等問題。同時,過度使用互斥鎖可能導致性能下降,因為線程可能會因等待鎖而阻塞。因此,在設計多線程程序時,需要仔細考慮互斥鎖的使用位置和時機,以達到最佳的性能和正確性平衡。