說說多執行緒pthread的基礎

2021-06-21 23:27:07 字數 4025 閱讀 4194

在c++程式設計中,尤其是在伺服器端,多執行緒幾乎是必備技能了。.net中自有微軟為大家封裝好的執行緒池與執行緒等等完備的方案,c++中,大部分資料都是集中在windows.h所支援的多執行緒操作,當然也有boost開源庫之類的。而今天我們要談的是在linux與unix中廣泛使用的pthread庫,當然比較簡略,筆者還是缺乏更多的實際經驗,畢竟也就剛開始接觸linux伺服器端。

依照慣例先推薦個資料:楊沙洲博士在2023年發表的《posix執行緒程式設計指南》,這本可以說是深入人心,而且一些概念都有提及,不過依然需要大家多多實踐和探索。

無論是pthread庫還是windows api,本質上都是一些小的模組,而我們的工作就是設計模組的結合,來達到我們的目的。

這篇文章只講三個最基礎的部分:基礎的pthread操作,互斥鎖,訊號機制。

i 基礎的pthread操作

總共只涉及四個基本元素:pthread_t,pthread_attr_t, pthread_create 和 pthread_join。

先來看一段程式,建立乙個執行緒。

#include #include using namespace std;

void* child_thread(void *arg)

int main()

如**所示,建立乙個執行緒就是如此的簡單,而執行緒要做的工作就是由我們的指標函式child_thread來承擔對應的工作。

如果你還不了解何為指標函式,請去《c++ primer》或是《effective c++》之類的經典讀本了解下相關知識。

如主程式main中所提到的,建立乙個執行緒,我們只需要兩樣東西:建立動作與唯一標識。

其中唯一標識就是pthread_t的工作,它用於顯示執行緒的id,無論去哪都不好改變。

而pthread_create這個函式,就是做了建立這個動作。

那麼乙個子執行緒是如何才能建立的呢?讀過作業系統之類書籍的一定知道,程序的建立實質上是乙個pcb控制塊的建立,而乙個pcb控制塊一般而言都包含了唯一標識、狀態、相應的程式等等,類似的子執行緒也是由這些部分組成的:識別符號、執行緒屬性(狀態)、控制程式的起始位址以及需要的引數。而它的流程是這樣的:

分配乙個唯一標識給即將建立的執行緒

呼叫建立動作並傳遞對應需要的引數。

這也就是我們上面程式所做的所有工作,乙個最基本的執行緒就建立完成了。

前面有提到執行緒屬性這個概念,其實屬性也就是規定了這個執行緒是個怎樣的型別,那也就是我們第三個元素pthread_attr_t的作用了。

一般而言,屬性的設定都是用pthread的自帶庫的設定函式來完成的。

pthread_attr_t attr;//attribute for threads

pthread_attr_init(&attr);

pthread_attr_setdetachstate(&attr, pthread_create_joinable);

其中第一句定義了乙個屬性例項,第二步通過預設初始化完成初始化,第三步則是呼叫了屬性設定允許執行緒同步。

剛剛提到了乙個很有意思的名字叫作:執行緒同步。這也是最麻煩的地方。

何為同步?

假設我們的主線程main函式完成所有工作需要10s,其中我們有兩個子執行緒叫作吃飯,睡覺。吃飯完成需要5s,睡覺完成需要20s。

然後的我們假設主線程在執行的第一秒內建立了子執行緒吃飯和子執行緒睡覺。

那麼會發生什麼?

在主線程10s完成所有工作的時候,子執行緒吃飯已經完成了,但子執行緒睡覺僅完成了不到一半,這下子問題來了,主線程都結束工作了,自然不允許你繼續睡下去,於是乎,全部結束。

而同步就是為了解決這類的問題。

何為同步,我們規定,必須先吃飯,後睡覺,睡覺完了才能幹別的事情。

那麼主線程建立好吃飯這個子執行緒,必須等待它完成,再去建立睡覺這個子執行緒,等睡覺完成,再做別的工作隨後結束。

沒有同步時:

#include #include #include using namespace std;

void* fan(void *arg)

void* sleep(void *arg)

int main()

執行結果

好亂好亂,這就是沒有同步造成的。即便這樣我們也可以看到只有吃飯的子執行緒完成了。

如何解決同步問題?這時候,我們的第四要素pthread_join登場,它會阻塞主線程直到子執行緒完成。

僅對main函式進行修改

int main()

執行結果:

這回一切正常。

至此,我們已經講述了基本的執行緒操作以及如何進行同步。

而接下來的互斥鎖與訊號機制實質上也是為了解決同步問題的兩類方法,它們解決了臨界資源的同步。

ii 互斥鎖

互斥鎖怎麼實現?首先,互斥鎖不是我們使用的變數,可以把它當成某個變數的標誌位,比如我們有乙個臨界資源cd,它對應的標誌位就是cd_mutex。假設這樣一種場景,一盤cd每次只能由乙個人藉走,而我們記錄它總共借出的次數,可以這一定義:

int cd_quantity = 0;

pthread_mutex_t cd_quantity_mutex;

乙個是本身的變數,乙個則是它的標誌位也叫鎖。那麼怎麼使用它呢?筆者在這裡僅給出偽**,請自行嘗試。

void* 子執行緒(void *arg)

int main()

這樣,通過互斥鎖,我們可以發現變數cd_quantity可以被有序修改。

當然根據設計的不同,有可能執行緒是混亂的。

iii 基礎訊號量機制

如果說互斥鎖可以解決臨界資源問題,訊號機制,在筆者看來更多的是解決執行順序問題,一般而言都是與互斥鎖搭配使用。

在這一部分,由於筆者自認為還不能很好完善地解釋,因而我給出了乙個基本示例,來自其它部落格:

也是筆者在找資料時發現的。

對**進行了精簡

pthread_mutex_t tasks_mutex; //互斥鎖

pthread_cond_t tasks_cond; //條件訊號變數,處理兩個執行緒間的條件關係,當task>5,hello2處理,反之hello1處理,直到task減為0

void* say_hello2( void* args )

else if( !is_signaled )

pthread_mutex_unlock( &tasks_mutex ); //解鎖

if( tasks == 0 )

break;

}

}void* say_hello1( void* args )

else

pthread_mutex_unlock( &tasks_mutex ); //解鎖

if( tasks == 0 )

break;

} }int main()

很多人看了還是會有點模糊,筆者來簡單解釋下:

pthread_cond_wait用於阻塞當前的執行緒,在阻塞的同時釋放鎖,等待pthread_cond_signal或pthread_cond_broadcast來喚醒。

pthread_cond_signal是傳送乙個訊號量給某個阻塞的程序,並喚醒它。

那麼在本例中is_signaled是用來幹嘛的?其實在用pthread_cond_signal進行喚醒阻塞程序的操作時,當前執行緒並沒有被阻塞或者強制結束,依然在執行!所以is_signaled相當於標誌位,避免該執行緒繼續爭奪資源。

詳細描述可以參考另一篇博文

至此,筆者簡單向大家介紹了多執行緒程式設計,但路漫漫,還有很多操作我們在這並沒有實現,而實際應用中也不是上面這些例子可以比擬的。希望大家和筆者一起進步,不斷學習。

等筆者感覺有所精益,再來和大家說道說道多執行緒吧~

the end.

pthread 多執行緒

多執行緒程式指的是在同乙個程式中多個執行流併發執行,它們共享程序的同乙個位址空間,分別完成相應的任務,並通過共享位址空間等方式完成執行緒間通訊,cpu按照時間片輪轉等方式對執行緒進行切換和排程。通常而言,執行緒共享的程序資源包括 linux中線程的建立依賴於lpthread.so 庫,建立乙個thr...

pthread建立多執行緒

include include include include include include include include include include include tinyxml tinyxml.h include include include include define macxm...

Linux 多執行緒 pthread

1.linux執行緒的發展 早在linux2.2核心中。並不存在真正意義上的執行緒,當時linux中常用的執行緒pthread實際上是通過程序來模擬的,也就是同過fork來建立 輕 程序,並且這種輕程序的執行緒也有個數的限制 最多只能有4096和此類執行緒同時執行。2.4核心消除了個數上的限制,並且...