UNPv1第二十三章 執行緒

2021-07-11 10:56:32 字數 4490 閱讀 5246

在傳統的unix模型中,當乙個程序需要由另乙個實體執行某件事時,該程序派生(fork)乙個子程序,讓子程序去進行處理。unix下的大多數網路伺服器程式都是這麼編寫的,這在我們的併發服務程式例子中可以看出:父程序接收連線,派生子程序,子程序處理與客戶的互動。

雖然這種模式很多年來使用的很好,但是fork有一些問題:

fork是昂貴的。記憶體映像要從父程序拷貝到子程序,所有描述字要在子程序中複製等等。目前的實現使用一種稱做寫時拷貝(copy-on-write)技術,可避免父程序資料空間向子程序的拷貝,除非子程序需要自己的拷貝。儘管有這種優化技術,fork仍然是昂貴的。

fork子程序後,需要用程序間通訊(ipc)在父子程序之間傳遞資訊。fork之前的資訊容易傳遞,因為子程序一開始就有父程序資料空間及所有描述字的拷貝。但是從子程序返回資訊給父程序需要做更多的工作。

執行緒有助於解決這兩個問題。執行緒有時候稱為輕權程序(lightweight process),因為執行緒比程序」輕權」。也就是說,建立執行緒要比建立程序塊10~100倍。

乙個程序中的所有執行緒共享相同的全域性記憶體,這使得執行緒很容易共享資訊,但是這種簡易性也帶來了同步(synchronization)問題。乙個程序中的所有執行緒不僅共享全域性變數,而且共享:

1. 程序指令

2. 大多數資料

3. 開啟的檔案(如描述字)

4. 訊號處理程式和訊號處置

5. 當前工作目錄

6. 使用者id和組id

但是每個執行緒有自己的:

1. 執行緒id

2. 暫存器集合,包括程式計數器和棧指標

3. 棧(用於存放區域性變數和返回位址)

4. errno

5. 訊號掩碼

6. 優先順序

講述5個基本執行緒函式,利用他們代替fork重新編寫我們的tcp客戶-伺服器程式

(1)當乙個程式由exec啟動時,會建立乙個稱為初始執行緒(initial thread)或主線程(main thread)的單個執行緒。額外執行緒則由pthread_create函式建立。

#include int pthread_create(pthread_t * tid, const pthread_attr_t * attr, void *  (*func)(void *), void * arg);  

//return: success 0, failed e***

乙個程序中的每個執行緒都由乙個執行緒id(thread id)標識,其資料型別是pthread_t(常常是unsigned int)。如果新的執行緒建立成功,其id將通過tid指標返回。

每個執行緒都有很多屬性(attribute):優先順序,起始棧大小,是否應該是乙個守護執行緒,得等。我們通常使用預設值,將attr引數說明為空指標。

最後,當建立乙個執行緒時,我們要說明乙個它將執行的函式。函式的位址由func引數指定,該函式的呼叫引數是乙個指標arg。如果我們需要多個呼叫引數,我們必須將它們打包成乙個結構,然後將其位址當作唯一的引數傳遞給起始函式。

注意func和arg的宣告,func函式取乙個通用指標(void )引數,並返回乙個通用指標(void )。這就使得我們可以傳遞乙個指標(指向任何我們想要指向的東西)給執行緒,由執行緒返回乙個指標(同樣地,指向任何我們想要指向的東西)。

(2)我們可以呼叫pthread_join等待乙個執行緒終止。把執行緒和unix程序相比,pthread_create類似於fork,pthread_join類似與waitpid。

#include 

int pthread_join(pthread_t tid, void * * status);

//返回:成功為0,出錯為正的e***值

我們必須指定要等待執行緒的tid。很可惜,我們沒有辦法等待任意乙個執行緒結束(類似於waitpid的程序id引數為-1的情況)。我們在討論圖23.14時還將涉及這個問題。如果,status指標非空,執行緒的返回值(乙個指向某個物件的指標)將存放在status指向的位置。

(3)每個執行緒都有乙個id以在給定的程序內標識自己。執行緒id由pthread_create返回,我們也看到了它在pthread_join中的使用。執行緒用pthread_self取得自己的執行緒id。

#include 

pthread_t pthread_self(void); //返回:呼叫執行緒的執行緒id

與unix程序相比,執行緒的pthread_self類似於getpid。

(4)執行緒或者是可匯合的(joinable)或者是脫離的(detached)。當可匯合的執行緒終止時,其執行緒id和退出狀態將保留,直到另外乙個執行緒呼叫pthread_join。脫離的執行緒則像守護程序:當它終止時,所有的資源都將釋放,我們不能等待它終止。如果乙個執行緒需要知道另乙個執行緒什麼時候終止,最好保留第二個執行緒的可匯合性。

pthread_detach函式將指定的執行緒變為脫離的。

#include 

int pthread_detach(pthread_t tid);

//返回:成功為0,出錯為正e***值

該函式通常被想脫離自己的執行緒呼叫,如: pthread_detach( pthread_self() );

(5)終止執行緒的一種方法是呼叫pthread_exit

#include 

void pthread_exit(void * status);

如果執行緒未脫離,其執行緒id和退出狀態將一直保留到呼叫程序中的某個其他執行緒呼叫pthread_join。

指標status不能指向區域性於呼叫執行緒的物件,因為執行緒終止時這些物件也消失。

有兩種其他方法可使執行緒終止:

1. 啟動執行緒的函式(pthread_create的第3個引數)返回。既然該函式必須說明為返回乙個void指標,該返回值便是執行緒的終止狀態。

2. 如果程序的main函式返回或者任何執行緒呼叫了exit,程序將終止,執行緒將隨之終止。

我們稱執行緒程式設計為併發程式設計(concurrent programming)或並行程式設計(parallel programming),因為多個執行緒可併發執行並訪問相同的變數。雖然我們剛剛討論的錯誤情形以單cpu系統為前提,但是如果執行緒a和執行緒b在多處理器系統的不同cpu上同時執行,潛在的錯誤同樣存在。在通常的unix程式設計中,我們沒有遇到這種併發程式設計問題,因為用fork時,除了描述字外,父程序和子程序不共享任何東西。但是,當我們討論程序間的共享記憶體時仍將遇到這類問題。

我們剛剛討論的問題,即多個執行緒修改乙個共享變數,是最簡單的問題。解決方法是用乙個互斥鎖(mutex, 代表mutual exclusion)保護共享變數。只有我們持有該互斥鎖才能訪問該變數。在pthreads中,互斥鎖是型別為pthread_mutex_t的變數。我們用下面兩個函式為互斥鎖加鎖和解鎖。

#include 

int pthread_mutex_lock(pthread_mutex_t * mptr);

//return: success 0, failed e***

int pthread_mutex_unlock(pthread_mutex_t * mptr);

//return: success 0, failed e***

如果我們試圖為乙個已被其他執行緒鎖住的互斥鎖加鎖,程式便會阻塞直到該互斥鎖被解鎖。

如果互斥鎖變數是靜態分配的,我們必須將它初始化為常值pthread_mutex_initializer

我們需要一中方法使得主迴圈進入睡眠,直到有一線程通知它某件事已就緒。條件變數(condition variable)加上互斥鎖可以提供這種功能。互斥鎖提供互斥機制,條件變數提供訊號機制。

在pthreads中,條件變數是乙個pthread_cond_t型別的變數。條件變數使用下述兩個函式:

#include 

int pthread_cond_wait(pthread_cond_t * cptr, pthread_mutex_t * mptr);

int pthread_cond_signal(pthread_cond_t * cptr);

//return: success 0, failed e***

第二個函式的名字中「signal」一詞不是指unix的sig***訊號。

為了防止競爭,條件變數的使用總是和乙個互斥鎖結合在一起。

設有兩個共享的變數 x 和 y,通過互斥量 mut 保護,當 x > y 時,條件變數 cond 被觸發。

int x, y;

pthread_mutex_t mut = pthread_mutex_initializer;

pthread_cond_t cond = pthread_cond_initializer;

//等待直到 x > y 的執行流程:

pthread_mutex_lock(&mut);

while (x <= y)

/* 對 x、y 進行操作 */

pthread_mutex_unlock(&mut);

C NutShell 第二十三章 任務並行

1.指定狀態物件 static void main string args static void go object state 2.taskcreationoptions 可以調整任務的執行方式。longrunning 通知排程器為任務指定乙個執行緒 prefe irness 會使任務排程器的排...

第二十三天

1 使用 koa 搭建伺服器 const koa require koa 路由請求 context ctx 上下文 包含req和res ctx.body hello koa2 2 如何配置 koa 路由 const koa require koa 建立路由物件 const router requir...

Vue 第二十三章v on事件處理

1.目的,因為在做前端開發的時候需要進行原生事件處理,以下 是使用兩個不同的點選事件來觸發對應的內容 比如第乙個是演示獲取之前的按鈕上的內容,第二個是獲取事件屬性 事件處理 stop prevent once 單擊事件會繼續傳播 阻止單擊事件會繼續傳播 xiaotaozi網 小桃子網 點選事件將只會...