Linux中使用使用互斥量(鎖)Mutex來操持多線程同步執行pthread_mutex_lock()



簡 述: 在 Linux 中,使用互斥量(互斥鎖🔐) Mutex 來給保證多線程 ,在訪問公共變量的時候能夠 “串行” 代碼。從而使得多線程正確的同步執行。關於多線程創建和使用可以參考前面幾篇的文章,爭取早日把 Linux 系統篇之 系統編程給發佈完系列的教程。

PS:好幾天沒有接着學習 Linux 的系統函數和理論知識了。可能是前面幾天有點忙了吧;時間流逝啊,總是這麼得快,,,,

關於互斥鎖的使用如下:

  • pthread_mutex_t *mutex; //創建一個鎖
  • pthread_mutex_init(); //初始化一個互斥鎖
  • pthread_mutex_lock(); //上鎖🔓,或者使用 pthread_mutex_trylock()
  • pthread_mutex_unlock(); //解鎖🔐
  • pthread_mutex_destroy; //銷燬互斥鎖

編程環境:

💻: uos20 📎 gcc/g++ 8.3 📎 gdb8.0

💻: MacOS 10.14 📎 gcc/g++ 9.2 📎 gdb8.3


背景鋪墊:

  • 程序:

    先寫一個多線程程序,A 和 B 兩個子線程一起數出輸出到 100000;然後在沒有鎖🔐的情況下,每一線程輪流執行 10 毫秒,模擬時間片輪轉切片。

  • 代碼程序:

    這個代碼還沒有加鎖,保護共享變量。完整代碼如下;

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    
    int g_num = 0; //在全局區域,共享
    #define MAXNUMBER 100000
    
    void* funA(void* arg);
    void* funB(void* arg);
    
    int main(int argc, char *argv[])
    {
        pthread_t pthreadA = 0;
        pthread_t pthreadB = 0;
        pthread_create(&pthreadA, nullptr, funA, nullptr);  //創建兩個子線程
        pthread_create(&pthreadB, nullptr, funB, nullptr);
    
        pthread_join(pthreadA, nullptr);  //阻塞,回收資源
        pthread_join(pthreadB, nullptr);
      
        return 0;
    }
    
    void* funA(void* arg)
    {
        for (int i = 0; g_num < MAXNUMBER; i++) {
            int a = g_num;  //27-29行,增加寄存器和內存之間的數據交換操作,使得出現內存沒來得及保存數據的現象的概率大一點
            a++;
            g_num = a;
            printf("A thread id: %ld,   num = %d\n", pthread_self(), g_num);
    
            usleep(10); //沉睡 10 毫秒, 模擬時間片輪轉,效果更明顯
        }
    
        return nullptr;
    }
    
    void* funB(void* arg)
    {
        for (int i = 0; g_num < MAXNUMBER; i++) {
            int b = g_num;
            b++;
            g_num = b;
            printf("B thread id: %ld,   num = %d\n", pthread_self(), g_num);
    
            usleep(10); //沉睡 10 毫秒, 模擬時間片輪轉,效果更明顯
        }
    
        return nullptr;
    }
    
  • 結果分析:

    下面截圖裏面,在 MacOS 10.14.6 和 Ubuntu 20.04 裏面跑,都是正確的結果;

    [疑惑]可能是因爲系統系統內核或者底層作了修改???又或者是數數的間隔過小,數的數字不多? 預判的結果是 實際數數的大小會比實際正確結果要小一點(原因是時間片輪轉的原因),待日後挖墳考古?難道是系統比較新?所以連續出現幾次都是最正確的結果。。。

    [解答]沒有出現內存來不及保存增加的數據,cpu 的時間片就被其他資源佔去了,導致最後出現的小數有可能出現在大數的後面打印,這是一個概率問題;而 27-29 行和 42-44 行,是刻意增加寄存器和內存之間的數據交換操作次數,使得出現內存沒來得及保存數據的現象的概率大一點。

    • 如果在 Ubuntu 16.04 裏面跑改程序,如果出現了最後一個數字比 100000 小一點,那麼解釋的可能如下:

      其中會發現結果有點混亂。這是兩個子線程互相操作同一個數據,時間片切換的時候,還新的數據還沒有來得及保存進內存裏面,就切換到了下一個線程中去了。

      • [實際結果]多數了一位,最後一個結果是100001,且有時候還會下面還會再打印一個小的數字
      • [期望結果]只數數到 100000;
      • 分析圖如下:

使用互斥量(鎖) Mutex:

如果我們想使用互斥鎖同步線程,那麼所有線程,只要是有訪問同一個共享公共變量的線程都要加鎖。

  • 使用流程步驟:
  1. 創建一把互斥鎖

    pthread_mutex_t *mutex
    
  2. 對互斥鎖進程初始化 init

    int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
    
  3. 使用互斥鎖:每個子線程要訪問公共變量的時候,都加上鎖,使用完後就解鎖

    //若果沒有上鎖,就上鎖;若果已經上鎖,直接返回,不阻塞
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    
    //若果沒有上鎖,就上鎖;若果已經上鎖,就阻塞該線程
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    
    //解鎖
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    
  4. 使用完畢,銷燬互斥鎖

    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    

改寫例子,使用互斥量(鎖)實例:

將上面的例子改了改,然後在 A、B 兩個子線程裏面,使用互斥量(鎖),會發現也會得到取其正確的結果。結果符合預期。

  • 修改後的加鎖例子:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int g_num = 0; //在全局區域,共享
#define MAXNUMBER 100000
pthread_mutex_t g_mutex; //創建全局的互斥鎖

void* funA(void* arg);
void* funB(void* arg);

int main(int argc, char *argv[])
{
    pthread_mutex_init(&g_mutex, nullptr); //對鎖進行初始化

    pthread_t pthreadA = 0;
    pthread_t pthreadB = 0;
    pthread_create(&pthreadA, nullptr, funA, nullptr);  //創建兩個子線程
    pthread_create(&pthreadB, nullptr, funB, nullptr);

    pthread_join(pthreadA, nullptr);  //阻塞,回收資源
    pthread_join(pthreadB, nullptr);

    pthread_mutex_destroy(&g_mutex);  //釋放互斥鎖資源
    return 0;
}

void* funA(void* arg)
{
    for (int i = 0; g_num < MAXNUMBER; i++) {
        pthread_mutex_lock(&g_mutex);
        int a = g_num;
        a++;
        g_num = a;
        printf("A thread id: %ld,   num = %d\n", pthread_self(), g_num);
        pthread_mutex_unlock(&g_mutex);

        usleep(10); //沉睡 10 毫秒, 模擬時間片輪轉,效果更明顯
    }

    return nullptr;
}

void* funB(void* arg)
{
    for (int i = 0; g_num < MAXNUMBER; i++) {
        pthread_mutex_lock(&g_mutex);
        int b = g_num;
        b++;
        g_num = b;
        printf("B thread id: %ld,   num = %d\n", pthread_self(), g_num);
        pthread_mutex_unlock(&g_mutex);

        usleep(10); //沉睡 10 毫秒, 模擬時間片輪轉,效果更明顯
    }

    return nullptr;
}
  • 運行結果:

    實際結果和預期結果一致。


下載地址:

18_mutex

歡迎 star 和 fork 這個系列的 linux 學習,附學習由淺入深的目錄。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章