自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux多線程同步機制-互斥鎖(mutex)

系統(tǒng) Linux
在使用互斥鎖時,需要注意正確的加鎖和解鎖順序,避免死鎖等問題。同時,過度使用互斥鎖可能導致性能下降,因為線程可能會因等待鎖而阻塞。因此,在設計多線程程序時,需要仔細考慮互斥鎖的使用位置和時機,以達到最佳的性能和正確性平衡。

引言

在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)用線程會阻塞,直到互斥量被解鎖。在完成了對共享資源的訪問后,要對互斥量進行解鎖。

互斥鎖特點如下:

  1. 原子性:把一個互斥量鎖定為一個原子操作,操作系統(tǒng)保證了如果一個線程鎖定了一個互斥量,沒有其他線程在同一時間可以再成功鎖定這個互斥量;
  2. 唯一性:如果一個線程鎖定了一個互斥量,在它解除鎖定之前,沒有其他線程可以鎖定這個互斥量;
  3. 非忙等待:如果一個線程已經(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ā)生時,兩個或多個線程被無限期地阻塞,因為它們在等待對方釋放鎖。以下是一些關于互斥鎖死鎖的信息:

  1. 死鎖的條件:死鎖通常發(fā)生在以下四個條件同時滿足時:

互斥:資源不能被多個線程同時訪問。

占有和等待:一個線程持有至少一個鎖,并且等待獲取其他線程持有的鎖。

不可剝奪:已經(jīng)獲得的鎖不能被其他線程強行剝奪,只能由持有它的線程釋放。

循環(huán)等待:存在一個線程持有鎖的循環(huán)鏈,每個線程都在等待下一個線程持有的鎖。

  1. 死鎖的避免:可以通過以下幾種策略來避免死鎖:

固定順序加鎖:總是以相同的順序獲取多個鎖。

超時嘗試加鎖:使用 pthread_mutex_trylock() 或其他帶有超時功能的加鎖方法。

一次性獲取所有鎖:在開始訪問共享資源前,先獲取所有需要的鎖。

使用死鎖檢測算法:定期檢測死鎖情況,并采取措施解決。

  1. 死鎖的解除:如果檢測到死鎖,可以采取以下措施:

剝奪資源:從其他線程剝奪資源,賦予死鎖線程。

撤銷進程:終止一個或多個線程或進程,打破死鎖狀態(tài)。

資源重分配:重新分配資源,以打破循環(huán)等待條件。

  1. 避免嵌套鎖:盡量減少鎖的嵌套使用,如果必須嵌套使用,確保內(nèi)層鎖總是不同類型的或者使用不同的加鎖順序。
  2. 鎖的分級管理:將鎖分級,高級別的鎖可以包含多個低級別的鎖,確保在請求高級別鎖時,已經(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;
}

在上述示例中,我們進行了如下操作:

  1. 首先,定義了兩個互斥鎖 mutex1 和 mutex2,以及一個標志 deadlockDetected 用于死鎖檢測(但在當前代碼中未實際使用)。
  2. thread1Function 和 thread2Function 分別是兩個線程的執(zhí)行函數(shù)。

thread1Function 先獲取 mutex1,等待 1 秒后再獲取 mutex2,最后釋放兩個鎖。

thread2Function 邏輯相同,也是先獲取 mutex1,等待 1 秒后獲取 mutex2,最后釋放。

  1. detectDeadlock 函數(shù)用于模擬死鎖檢測,但由于當前固定了加鎖順序,實際上不會發(fā)生死鎖,所以此函數(shù)內(nèi)未進行真正的檢測操作。
  2. 在 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;
}

在上述示例中,我們進行了如下操作:

  1. 首先,在程序中定義了兩個互斥鎖 lock1 和 lock2,用于控制線程對資源的訪問。
  2. thread1 函數(shù):

它通過一個無休止的 while 循環(huán)來不斷嘗試獲取 lock1 鎖。

一旦成功獲取,會輸出相應的成功獲取信息,并立即跳出當前的循環(huán)。

倘若獲取失敗,會輸出失敗提示,并等待 1 秒鐘,然后繼續(xù)下一輪的獲取嘗試。

在成功獲取 lock1 之后,經(jīng)過 1 秒鐘的模擬操作,再次進入另一個 while 循環(huán),嘗試獲取 lock2 。

若獲取 lock2 失敗,會輸出失敗信息,同時釋放之前已獲取到的 lock1 ,接著等待 1 秒鐘,再次進行獲取嘗試。

當成功獲取到 lock2 后,按順序釋放 lock2 和 lock1 ,完成線程的操作。

  1. thread2 函數(shù):

其邏輯與 thread1 相似。首先通過無限的 while 循環(huán)嘗試獲取 lock2 。

成功獲取時輸出信息并跳出循環(huán),失敗則輸出提示、等待 1 秒后重新嘗試。

在成功獲取 lock2 并經(jīng)過 1 秒模擬操作后,進入新的循環(huán)嘗試獲取 lock1 。

若獲取 lock1 失敗,同樣輸出失敗提示,釋放 lock2 ,等待 1 秒后重試。

最終成功獲取并按序釋放 lock1 和 lock2 。

  1. 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)境中的同步機制,具有以下關鍵特點和用途:

  1. 資源保護:確保在同一時刻只有一個線程能夠訪問被保護的共享資源,防止數(shù)據(jù)競爭和不一致性。
  2. 原子操作:保證對共享資源的操作是原子性的,即要么完全執(zhí)行,要么完全不執(zhí)行,避免中間狀態(tài)被其他線程觀察到。
  3. 同步協(xié)調(diào):使多個線程能夠按照預定的順序和條件進行協(xié)作,避免混亂和錯誤。
  4. 互斥性:當一個線程獲取互斥鎖后,其他線程在該鎖被釋放之前無法獲取,從而實現(xiàn)對關鍵代碼段或資源的獨占訪問。

在使用互斥鎖時,需要注意正確的加鎖和解鎖順序,避免死鎖等問題。同時,過度使用互斥鎖可能導致性能下降,因為線程可能會因等待鎖而阻塞。因此,在設計多線程程序時,需要仔細考慮互斥鎖的使用位置和時機,以達到最佳的性能和正確性平衡。

責任編輯:武曉燕 來源: Linux二進制
相關推薦

2024-07-05 08:32:36

2024-07-08 12:51:05

2024-07-25 11:53:53

2010-01-21 11:27:30

linux多線程機制線程同步

2020-09-28 06:49:50

Linux系統(tǒng)編程互斥量mutex

2010-03-15 16:31:34

Java多線程

2020-08-26 08:59:58

Linux線程互斥鎖

2011-11-23 10:09:19

Java線程機制

2021-03-24 08:02:58

C語言

2025-02-17 02:00:00

Monitor機制代碼

2024-06-24 08:10:00

C++互斥鎖

2023-12-24 12:33:20

互斥鎖Go代碼

2023-06-02 08:29:24

https://wwMutex

2023-06-09 07:59:37

多線程編程鎖機制

2017-12-15 10:20:56

MySQLInnoDB同步機制

2009-11-28 20:24:13

Linux互斥鎖同步移植

2019-05-27 14:40:43

Java同步機制多線程編程

2025-03-31 00:01:12

2012-07-27 10:02:39

C#

2024-10-14 08:51:52

協(xié)程Go語言
點贊
收藏

51CTO技術棧公眾號