pthread 入門

ChrisZZ發表於2024-05-03

pthread 入門

Author: ChrisZZ
Link: https://www.cnblogs.com/zjutzz
Time: 2024-05-03 23:55:21

0. 目的

pthread 是C介面的多執行緒庫,使用廣泛:linux-x64, macOS, android, 各種車載嵌入式的 linux 系統。

使用 pthread 可以做多執行緒計算,提高程式整體效率。

不使用C11或C++11的原因: C 比較簡潔, 有些手機平臺的 C++11 多執行緒據說有bug。

網路上搜尋 “C 多執行緒”,靠前的結果總是被 “muduo網路” 和 “C++11多執行緒” 的資料覆蓋, 專門搜 pthread 發現很多部落格只寫了一兩個技術點,還不乏錯誤寫法。

本篇撥亂反正。

1. 概念

  • 互斥鎖 mutex: 用來保護變數的排他性訪問
  • 臨界區 critical section: 只應當被單個執行緒執行的程式碼片段.
  • 資料競爭 data race: >=2 個執行緒函式訪問變數,並且其中至少有一個執行緒在修改它
  • 執行緒洩漏 thread leak: 執行緒建立後沒有被回收,例如子執行緒建立後沒被主執行緒等待,也沒自行detach
  • 條件變數 condition variable: 用於執行緒同步,通常一個普通變數搭配使用

2. pthread API

線上文件 https://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread.h.html

本地文件 man 3 pthreat_create

弄懂最常用的幾個API可以解決不少問題了:

#include <pthread.h>

int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void*(*start_routine)(void*), void* arg);
int pthread_join(pthread_t thread, void **value_ptr);

int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
pthread_mutex_t cond = PTHREAD_MUTEX_INITIALIZER;

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t* attr);
int pthread_cond_destroy(pthread_cond_t* cond);
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int pthread_t pthread_self(void);

3. 用 tsan 檢查執行緒問題

首先編譯連結時開啟tsan:

gcc xxx.c -g -fsanitize=thread

執行程式,檢視tsan給出的報告,主要兩類:

  • data race:資料競爭
  • thread leak: 執行緒洩漏

4. 虛假喚醒

使用條件變數時通常要這樣寫:

pthread_mutex_lock(&mutex);
while (determine(xxx))
{
    pthread_cond_wait(&cond, &mutex);
}
change(xxx);
pthread_mutex_unlock(&mutex);

用 while 而不是 if, 是為了避免虛假喚醒。兩類虛假喚醒:

  • 作業系統讓 pthrad_cond_wait 醒來, 並沒有別的執行緒傳送 signal。 在Linux Pthread 上這其實不可能發生
  • 有多個子執行緒,每個子執行緒都執行上述程式碼,如果有>=2個子執行緒,可能第一個執行緒在執行後,破壞了條件,導致第二個執行緒繼續執行 change(xxx) 時發生非預期結果(如記憶體訪問越界、計算結果不對等)

5. 臨界區

pthread_mutex_lock(&mutex);
do something...
pthread_mutex_unlock(&mutex);

其中 do something... 可以是一條語句,也可以是多條語句。 統稱為臨界區。

形象比喻: 馬路上開車, 本來是雙行道, 前方突然變為單行道,延續幾百米後又變成雙行道。 這段單行道就是臨界區, 進入和離開臨界區的 lock、unlock 行為, 可以認為是紅綠燈。

6. 有了 atomic 變數就不用 mutex 了嗎?

顯然不對。 臨界區的定義應當根據你實際執行的程式碼、實際的業務邏輯來判斷。

7. 條件變數怎麼用

通常是條件變數和一個普通變數繫結使用。

pthread_cond_signal() 和 pthread_cond_wait() 都是各自臨界區的一部分, 也就是說不應該放到 pthread_mutex_lock() 和 pthread_mutex_unlock() 的外部。

pthread_cond_wait() 本身的文件明確指出: 它會執行釋放mutex鎖、執行等待,當條件被signal了就恢復,並且鎖住 mutex, 因此不需要自行畫手添足得給 cond_wait() 前增加 unlock 操作。

pthread_mutex_lock(&mutex);
  xxxxx
  pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);


pthread_mutex_lock(&mutex);
if ( false == testCondition )
    pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);

8. 封裝

假設想要在 Windows 上也是用 pthread API, 不推薦用 MinGW 因為很多三方庫沒法編譯連結,自己編譯不現實。

pthreads-win32 是一個封裝庫, pthreads.h 裡的常用 API 封裝的還行。

ncnn 提供了 Windows 多執行緒和 Pthreads API 的封裝, 整體風格是 pthreads 風格的: https://github.com/Tencent/ncnn/blob/master/src/platform.h.in

9. 其他高階主題

  • 設定執行緒優先順序
  • 實現執行緒池
  • 各種花樣的生產者消費者問題

相關文章