Linux中原子操作,死鎖原因以及解決方法,讀寫鎖的屬性和函數使用pthread_rwlock_wrlock()


簡 述: 上一篇中介紹了多線程使用互斥量(鎖)來控制程序的訪問公共資源的時候是”串行“的;本篇繼續,重點講解如下幾個概念:Linux 中的原子操作死鎖原因及解決方法 、和讀寫鎖 和對應的源碼小例子。其中讀寫鎖的使用例子,完全可以參考互斥量(鎖),其大概流程如下:

  • pthread_rwlock_init()
  • pthread_rwlock_rdlock() / pthread_rwlock_tryrdlock() / pthread_rwlock_wrlock() / pthread_rwlock_trywrlock()
  • 、、、代碼片
  • pthread_rwlock_unlock()
  • pthread_rwlock_destroy()

編程環境:

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

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


原子操作:

  • 原子操作:

    • cpu 處理一個指令,線程 / 進程在處理完這個指令之前,是不會失去 cpu 的。

借用顯示生活中的知識,原子⚛是最小的不可分割的物質,沒有比它更小的(類比,不詳探究夸克);在一個程序中,是有幾百行代碼構成了,可以將一行代碼(一行表達式語句)看做爲一個 ”原子操作“

比如:

printf("");
int a = b + 100;

  • 臨界區:

    • 從代碼的角度理解,就是 執行加鎖語句 pthread_mutex_lock() 和解鎖語句 pthread_mutex_unlock() 之間代碼片,稱之爲 臨界區; 也可以看作爲 ”僞原子操作“ ,因爲它有可能臨界區的代碼執行到一半,cpu 就被搶走了,但是其雖然搶到了 cpu 但是會阻塞,或者不能夠訪問該臨界區的代碼片,然後等待輪轉,cpu 再次回來,繼續在自己身上繼續執行接下來的代碼行;然後這樣臨界區的代碼就只有它執行完畢了。可以看做是一個 ”僞“ 原子操作。

    示意圖如下:


造成死鎖的原因:

自己鎖自己:

  • 分析: 當遇到連續鎖兩次的時候,線程會阻塞在 第二個 pthread_mutex_lock() 函數這一行裏面。

循環鎖住:


避免死鎖的方式:

避免或者解決死鎖的三種方式如下:

  • 讓線程按照一定的順序訪問共享資源
  • 在訪問其他鎖的時候,需要先將自己的鎖解開
  • 設置上鎖的使用,可以使用 pthread_mutex_trylock() 函數

讀寫鎖:

除了使用互斥量(鎖)之外,還可以採用 讀寫鎖 來控制多線程訪問共享資源。

讀寫鎖的理解:

  • 讀鎖 - 對內存做讀操作
  • 寫鎖 - 對內存做寫操作

讀寫鎖的特性:

  • 線程 A 加鎖成功,又來了三個線程,做讀操作,可以加鎖成功
    • 讀共享 - 並行處理
  • 線程 A 加寫鎖成功,又來了三個線程,做讀操作,三個線程阻塞
    • 寫獨佔
  • 線程 A 加讀鎖成功,又來了 B 線程加寫鎖線程阻塞,又來了 C 線程加讀鎖阻塞
    • 讀寫不能同時進行
    • 寫的優先級高(即使後面線程有先後來順序來,也會看一下優先級)

讀寫鎖的場景練習:

上面的讀寫鎖的特性 可以看做是理論部分,然後這裏用幾個實際場景進行一下分析:

  • 線程 A 加寫鎖成功,線程 B 請求讀鎖
    • 線程 B 阻塞
  • 線程 A 持有讀鎖,線程 B 請求寫鎖
    • 線程 B 阻塞
  • 線程 A 擁有讀鎖,線程 B 請求讀鎖
    • 線程 B 加鎖成功
  • 線程 A 持有讀鎖,然後線程 B 請求寫鎖,然後線程 C 請求讀鎖
    • B 阻塞,C 阻塞 -寫的優先級高
    • A 解鎖,B 線程加寫鎖成功,C繼續阻塞
    • B 解鎖,C 加讀鎖成功
  • 線程A持有寫鎖,然後線程B請求讀鎖,然後線程C請求寫鎖

讀寫鎖的使用場景:

  • 互斥鎖 - 讀寫串行
  • 讀寫鎖:
    • 讀:並行
    • 寫:串行
  • 程序中的 “讀操作” 大於 ”寫操作“ 的時候,比如說 12306 買火車票的例子 ,就有大量的率新讀取數據,遠大於買票的時候寫操作。

讀寫鎖的主要操作函數:

讀寫鎖的使用流程和互斥量(鎖)的流程基本一樣。

  • 初始化讀寫鎖

    int pthread_rwlock_init(pthread_rwlock_t *lock, const pthread_rwlockattr_t *attr);
    
  • 銷燬讀寫鎖

    int pthread_rwlock_destroy(pthread_rwlock_t *lock);
    
  • 加讀鎖

    int pthread_rwlock_rdlock(pthread_rwlock_t *lock);
    
  • 嘗試加讀鎖

    int pthread_rwlock_tryrdlock(pthread_rwlock_t *lock);
    
  • 加寫鎖

    int pthread_rwlock_wrlock(pthread_rwlock_t *lock);
    
  • 嘗試加寫鎖

    int pthread_rwlock_trywrlock(pthread_rwlock_t *lock);
    
  • 解鎖

    int pthread_rwlock_unlock(pthread_rwlock_t *lock);
    

寫一個運用讀寫鎖的例子:

上面例子講了這麼多用法和屬性作爲鋪墊,這裏有一代碼例子,講解讀寫鎖的使用例子:

  • 需求練習:

    • 3 個線程不定時寫同一全局資源,5 個線程不定時讀同一全局資源
  • 代碼:

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    
    int g_number = 0;
    pthread_rwlock_t lock;
    
    void* writeFunc(void* arg);
    void* readFunc(void* arg);
    
    int main(int argc, char *argv[])
    {
        pthread_t p[8];
    
        pthread_rwlock_init(&lock, nullptr); //初始化一個鎖
    
        for (int i = 0; i < 3; i++) {  //創建寫線程
            pthread_create(&p[i], nullptr, writeFunc, nullptr);
        }
    
        for (int i = 3; i < 8; i++) {  //創建讀線程
            pthread_create(&p[i], nullptr, readFunc, nullptr);
        }
    
        for (int i = 0; i < 8; i++) {  //阻塞回收子線程的 pcb
            pthread_join(p[i], nullptr);
        }
    
        pthread_rwlock_destroy(&lock); //銷燬讀寫鎖,釋放鎖資源
        
        return 0;
    }
    
    void* writeFunc(void* arg)
    {
        while (true) {
            pthread_rwlock_wrlock(&lock); //加寫鎖
            g_number++;
            printf("--write: %lu, %d\n", pthread_self(), g_number);
            pthread_rwlock_unlock(&lock);  //解鎖
            usleep(500);
        }
        
        return nullptr;
    }
    
    void* readFunc(void* arg)
    {
        while (true) {
            pthread_rwlock_rdlock(&lock);  //加讀鎖
            printf("--read : %lu, %d\n", pthread_self(), g_number);
            pthread_rwlock_unlock(&lock);  //解鎖
            usleep(500);
        }
    
        return nullptr;
    }
    
  • 運行效果:

    • 屏蔽去掉加鎖解鎖的幾行註釋,則會出現以下異常情況,小數可能在大數後面再執行打印語句:

    • 加上讀寫鎖之後,得到預期正確結果,大數只會出現在小數後面打印


下載地址:

19_pthread_rwlock_wrlock

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

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