12 9 執行緒與fork

2022-05-14 03:25:20 字數 4122 閱讀 9070

當乙個執行緒呼叫函式fork的時候,整個程序位址空間會被拷貝到子程序中,在8.3節中有提到copy-on-write.子程序是乙個與父程序完全不同的程序,但是如果父程序和子程序都沒有對記憶體內容進行修改,那麼該記憶體頁就可以在父程序與子程序之間進行共享。

通過繼承父程序的整個位址空間,子程序也會繼承父程序每個互斥鎖,讀寫鎖以及條件變數的狀態,如果父程序包含了多個執行緒,而且在fork函式返回之後並不會立即呼叫exec的話,子程序就需要清除鎖狀態。

在fork後的子程序內部,只會出現乙個執行緒,它是父程序中呼叫fork函式的執行緒的拷貝。如果父程序中任何執行緒鎖定了鎖,相同的鎖在子程序中也會處於鎖定狀態,問題是子程序並沒有包含鎖定鎖的執行緒的拷貝,因此子程序沒有辦法知道哪乙個鎖需要鎖定以及哪乙個鎖需要解除鎖定。

上述問題可以通過如下方法避免:在fork之後呼叫函式exec,在這種情況下,老的位址空間將被拋棄,因此鎖定狀態並不重要。然而,這種方法並不總是可行的,如果子程序需要繼續執行,那麼我們就需要使用乙個不同的策略。

為了避免在乙個多執行緒程序中不一致的狀態,posix.1指出在fork返回之後到exec函式之前的時間內只能呼叫非同步訊號安全的函式。這限制了子程序在呼叫exec之前可以做的事情,但是並不能解決子程序中鎖狀態的問題。

為了清除鎖狀態,我們可以建立fork handler來進行處理。

#include

int

pthread_atfork

(void

(*prepare

)(void

),void

(*parent

)(void

),void

(*child

)(void

));

returns:0

ifok

,error number on failure

.

使用函式pthread_atfork,我們可以建立起三個函式來幫組清除鎖的鎖定狀態。prepare函式在父程序呼叫函式fork建立子程序之前被父程序呼叫,該fork handler的作用是獲取父程序定義的所有鎖。parent fork handler是在父程序fork了子程序但是fork函式還沒有返回之前由父程序呼叫執行的,該fork handler的作用是解除所有prepare fork handler獲取到的鎖的鎖定狀態;child fork handler在子程序中fork函式返回之前被呼叫,就像parent fork handler一樣,child fork handler必須釋放所有prepare fork handler獲取到的鎖。

注意,這些鎖並沒有被鎖定一次,解鎖兩次,因為在子程序被建立的時候,它獲取到了父程序定義的鎖的所有狀態,因為prepare鎖定了所有鎖,父程序和子程序會在相同的記憶體內容下開始執行,當父程序以及子程序分別解除它們鎖的拷貝的鎖定狀態的時候,新的記憶體空間將被分配給子程序,並且父程序的記憶體內容將被拷貝到子程序(copy-on-write),所以看起來就是父程序鎖定了父程序以及子程序的所有的鎖,然後父程序和子程序分別解除兩份處於不同位址空間的鎖的鎖定狀態,就像執行了如下的乙個序列:

父程序鎖定所有的鎖;

子程序鎖定所有的鎖;

父程序釋放鎖;

子程序釋放鎖;

我們可以呼叫函式pthread_atfork多次,從而可以建立多個fork handler的集合,如果我們不需要使用其中任何乙個handlers,可以傳入乙個null指標即可,這並不會產生什麼問題。當多個fork handlers被呼叫的時候,handlers被呼叫的順序是不一樣的,parent以及child fork handlers按照它們被註冊的順序進行呼叫,然而prepare函式會以它們被註冊順序的反序被呼叫。這一順序允許多個模組註冊它們自己的fork handlers並且保持鎖定的層次結構。

舉例來講,假設模組a呼叫模組b中的函式,同時兩個模組都有各自的鎖,如果鎖定層次是a在b之前,模組b必須在模組a之前安裝fork handlers.當父程序呼叫函式fork的時候,如下步驟將會被執行,假設子程序在父程序之前開始執行:

模組a中的prepare fork handler被呼叫來獲取模組a的鎖;

模組b中的prepare fork handler被呼叫來獲取模組b的鎖;

子程序被建立;

模組b的child fork handler被呼叫來釋放子程序中所有模組b的鎖;

模組b的child fork handler被呼叫來釋放子程序中所有模組a的鎖;

fork函式返回到子程序;

模組b的parent fork handler被呼叫來釋放所有模組b的鎖;

模組a的parent fork handler被呼叫來釋放所有模組a的鎖;

fork函式返回到父程序;

如果使用fork handlers來清除鎖的狀態的話,條件變數的狀態需要如何清除呢?在一些實現上,條件變數可能並不需要任何清除工作,然而,使用鎖作為條件變數的一部分的實現需要執行清除工作,但是問題是沒有提供介面供我們實現清除工作,如果鎖被嵌入到了條件變數的資料結構中,那麼我們在呼叫fork函式以後不能在使用條件變數了(只是在子程序中由這一限制吧???),因為沒有可移植的介面來清除其狀態,另一方面,如果實現使用了乙個全域性鎖來保護條件變數資料結構。那麼實現本身可以在fork函式庫中實現清理工作,然而,應用程式不應該依賴與這樣的細節。

圖12.17中的程式闡述了函式pthread_atfork以及fork handler的使用:

#include

"apue.h"

#include

pthread_mutex_t

lock1

=pthread_mutex_initializer

;

pthread_mutex_t

lock2

=pthread_mutex_initializer

;

void

prepare

(void

)

void

parent

(void

)

void

child

(void

)

void

*thr_fn

(void

*arg

)

int

main

(void

)

圖12.17 pthread_atfork example

執行效果如下所示:

yuekunhu@debian

:~/apue/

chapter12$

./12

_17.

exe

thread started

...

parent about to fork

...

prepare locks

...

parent unlocking locks

...

parent returned

from

fork

child unlocking locks

...

child returned

from

fork

yuekunhu@debian

:~/apue/

chapter12$

雖然pthread_atfork機制想要解決fork之後鎖狀態的問題,但是由於一些缺陷導致該機制只能在受限的環境下使用:

來自為知筆記(wiz)

多執行緒與fork

多執行緒程式裡盡量不使用fork 在多執行緒程式裡,在 自身以外的執行緒存在的狀態 下一使用fork的話,就可能引起各種各樣的問題.比較典型的例子就是,fork出來的子程序可能會死鎖.請不要,在不能把握問題的原委的情況下就在多執行緒程式裡fork子程序,能引起什麼問題呢?那看看例項吧.一執行下面的 ...

聊聊Linux下fork與執行緒池

這個知識點也是老生常談了,咱們今天就把他講清楚弄明白。執行緒池是指在乙個多執行緒程式中建立乙個執行緒集合,在執行新的任務的時候不是新建乙個執行緒,而是使用執行緒池中已建立好的執行緒,一旦任務執行完畢,執行緒就會休眠等待新的任務分配下來。這麼看來,執行緒池的優點就很明顯了,在頻繁的執行緒切換環境中,執...

fork程序與快取

題目描述 請問下面的兩個程式各一共輸出多少個 include include include int main void return0 include include include int main void return0 答案 第乙個程式輸出8個 第二個程式輸出6個 解析 網路 fork的重...