pthreads 和 vfork
我正在嘗試檢查 pthread 在其中一個執行 vfork 時真正發生了什麼。規範說父“控制執行緒”被“暫停”,直到子程序呼叫 exec* 或 _exit。據我了解,共識是這意味著整個父程序(即:及其所有pthreads)被暫停。我想通過實驗來確認它。到目前為止,我進行了幾次實驗,所有這些都表明其他 pthread 正在執行。由於我沒有 linux 經驗,我懷疑我對這些實驗的解釋是錯誤的,學習這些結果的真實解釋有助於避免我生活中進一步的誤解。所以這裡是我做的實驗:
實驗一
#include<unistd.h> #include<signal.h> #include<errno.h> #include<cstring> #include<string> #include<iostream> using namespace std; void * job(void *x){ int pid=vfork(); if(-1==pid){ cerr << "failed to fork: " << strerror(errno) << endl; _exit(-3); } if(!pid){ cerr << "A" << endl; cerr << "B" << endl; if(-1 == execlp("/bin/ls","ls","repro.cpp",(char*)NULL)){ cerr << "failed to exec : " << strerror(errno) << endl; _exit(-4);//serious problem, can not proceed } } return NULL; } int main(){ signal(SIGPIPE,SIG_IGN); signal(SIGCHLD,SIG_IGN); const int thread_count = 4; pthread_t thread[thread_count]; int err; for(size_t i=0;i<thread_count;++i){ if((err = pthread_create(thread+i,NULL,job,NULL))){ cerr << "failed to create pthread: " << strerror(err) << endl; return -7; } } for(size_t i=0;i<thread_count;++i){ if((err = pthread_join(thread[i],NULL))){ cerr << "failed to join pthread: " << strerror(err) << endl; return -17; } } }
有 44 個 pthread,所有這些執行緒都執行 vfork,並在子程序中執行 exec。每個子程序在 vfork 和 exec “A” 和 “B” 之間執行兩個輸出操作。該理論表明,輸出應為 ABABABABABABA ……沒有嵌套。然而,輸出是一團糟:例如:
AAAA BB B B
實驗二
懷疑在 vfork 之後使用 I/O lib 可能是個壞主意,我已將 job() 函式替換為以下內容:
const int S = 10000000; int t[S]; void * job(void *x){ int pid=vfork(); if(-1==pid){ cerr << "failed to fork: " << strerror(errno) << endl; _exit(-3); } if(!pid){ for(int i=0;i<S;++i){ t[i]=i; } for(int i=0;i<S;++i){ t[i]-=i; } for(int i=0;i<S;++i){ if(t[i]){ cout << "INCONSISTENT STATE OF t[" << i << "] = " << t[i] << " DETECTED" << endl; } } if(-1 == execlp("/bin/ls","ls","repro.cpp",(char*)NULL)){ cerr << "failed to execlp : " << strerror(errno) << endl; _exit(-4); } } return NULL; }
這一次,我執行了兩個循環,第二個循環撤消了第一個循環的結果,所以最後全域表
t[]
應該回到初始狀態(根據定義全為零)。如果進入子程序凍結了其他 pthread 使它們無法呼叫 vfork 直到目前子程序完成循環,那麼數組最後應該全為零。我確認當我使用 fork() 而不是 vfork() 時,上面的程式碼不會產生任何輸出。但是,當我將 fork() 更改為 vfork() 時,會向標準輸出報告大量的不一致。實驗三
這裡描述了另一個實驗https://unix.stackexchange.com/a/163761/88901 - 它涉及呼叫睡眠,但實際上當我用長
for
循環替換它時結果是相同的。
Linux 手冊頁
vork
非常具體:
vfork()
不同之處在於fork(2)
呼叫執行緒被掛起,直到子程序終止這不是整個過程,而是呼叫執行緒。POSIX 或其他標準不保證這種行為,其他實現可能會做不同的事情(最多並包括簡單地
vfork
使用 plain實現fork
)。(Rich Felker 還注意到vfork 中的這種行為被認為是危險的。)
在多執行緒程序中使用
fork
已經很難推理了,呼叫vfork
至少同樣糟糕。您的測試充滿了未定義的行為,您甚至不允許在 ’d 子級中呼叫函式(更不用說執行 I/O)了vfork
,除了exec
-type 函式和_exit
(甚至不是exit
,並且返回會導致混亂)。這是一個改編自您的範例,我認為它幾乎沒有未定義的行為,假設編譯器/實現不輸出對
int
s 進行原子讀取和寫入的函式呼叫。start
(一個問題是在 -之後的寫入是vfork
不允許的。)省略了錯誤處理以保持簡短。#include<unistd.h> #include<signal.h> #include<errno.h> #include<atomic> #include<cstring> #include<string> #include<iostream> std::atomic<int> start; std::atomic<int> counter; const int thread_count = 4; void *vforker(void *){ std::cout << "vforker starting\n"; int pid=vfork(); if(pid == 0){ start = 1; while (counter < (thread_count-1)) ; execlp("/bin/date","date",nullptr); } std::cout << "vforker done\n"; return nullptr; } void *job(void *){ while (start == 0) ; counter++; return NULL; } int main(){ signal(SIGPIPE,SIG_IGN); signal(SIGCHLD,SIG_IGN); pthread_t thread[thread_count]; counter = 0; start = 0; pthread_create(&(thread[0]), nullptr, vforker, nullptr); for(int i=1;i<thread_count;++i) pthread_create(&(thread[i]), nullptr, job, nullptr); for(int i=0;i<thread_count;++i) pthread_join(thread[i], nullptr); }
這個想法是這樣的:普通執行緒在增加全域原子計數器之前
start
等待(忙循環)原子全域變數。在 vfork 子程序中執行set的1
執行緒,然後等待(再次忙循環)其他執行緒增加計數器。vfork``start``1
如果其他執行緒在 期間掛起
vfork
,則永遠無法取得進展:掛起的執行緒永遠不會增加(它們在設置為counter
之前已經掛起),因此 vforker 執行緒將陷入無限忙等待。start``1