多線程中fork的坑
問題所在
在寫oj的時(shí)候,由于使用了線程池履植,并且在獲取用戶程序運(yùn)行結(jié)果的時(shí)候使用的是管道進(jìn)行子進(jìn)程的標(biāo)準(zhǔn)輸出的獲取阔涉,
最后帶來了一個(gè)問題,就是發(fā)現(xiàn)本來線程池有5個(gè)任務(wù)弃锐,最后調(diào)試信息的打印確沒有5個(gè)袄友,而且個(gè)數(shù)不確定殿托。
初步懷疑
是不是線程池出現(xiàn)了死鎖的情況。
添加線程池任務(wù)
void addTask(clTask* newTask)
{
pthread_mutex_lock(&pthreadMutex);
allTask.push(newTask);
pthread_mutex_unlock(&pthreadMutex);
pthread_cond_broadcast(&pthreadCond);
}
線程主函數(shù)
void* clPthread::pthreadMain(void *arg)
{
clPthread& self = *(clPthread*)arg;
while(1)
{
pthread_mutex_lock(&(self.pthreadMutex));
while(0 == self.allTask.size())
{
//阻塞在條件變量上并且解鎖互斥鎖
pthread_cond_wait(&(self.pthreadCond),&(self.pthreadMutex));
//條件變量滿足后剧蚣,加鎖
}
clTask* dealTask = self.allTask.front();
self.allTask.pop();
self.dealTask.push(dealTask);
pthread_mutex_unlock(&(self.pthreadMutex));
dealTask->run();
}
}
這是很標(biāo)準(zhǔn)的的生產(chǎn)者消費(fèi)者模型了支竹,應(yīng)該不會(huì)出現(xiàn)死鎖的情況。但還是懷疑鸠按,所以后來我在每次線程池運(yùn)行的任務(wù)的地方打印調(diào)試信息礼搁,
發(fā)現(xiàn)任務(wù)是都執(zhí)行了的,并且是5次目尖,并沒有出現(xiàn)死鎖的情況馒吴。那么問題可能出現(xiàn)在下面的代碼中了。
找到問題
再往下面走就是編譯和運(yùn)行的函數(shù)了,oj的編譯和運(yùn)行我是采用fork進(jìn)程然后重定向子進(jìn)程的標(biāo)準(zhǔn)輸出來獲取用戶程序的編譯錯(cuò)誤信息和
運(yùn)行情況的饮戳,獲取子進(jìn)程狀態(tài)和回收子進(jìn)程使用的是wait函數(shù)豪治,至于為什么使用wait函數(shù),因?yàn)槲业南敕ㄊ亲尭高M(jìn)程阻塞等待子進(jìn)程的編譯和運(yùn)行情況扯罐。
<b>問題就出現(xiàn)在這里</b>:
由于我是在線程中fork的子進(jìn)程负拟,子進(jìn)程結(jié)束的時(shí)候會(huì)觸發(fā)wait函數(shù)
但是此時(shí)的wait函數(shù)的觸發(fā)其實(shí)不一定是之前的線程fork的子進(jìn)程觸發(fā)的,可能是其他線程fork的子進(jìn)程歹河,因?yàn)樗芯€程fork的子進(jìn)程的ppid都一樣
等于是所有的子進(jìn)程結(jié)束都會(huì)使得wait蘇醒掩浙,但是這個(gè)wait確不是應(yīng)該蘇醒的那一個(gè)。
void clCompiler::compile()
{
int fd[2];
int ret = pipe(fd);
int res;
pid_t pid;
chdir(workDir.c_str());
solution->prepareCode();
pid = fork();
if(pid > 0){
char buf[1024];
close(fd[1]);
//問題所在的地方
wait(&res);
read(fd[0],buf,sizeof(buf));
close(fd[0]);
if(res == 0){
//編譯成功
this->status = COMPLIE_OK;
} else{
//編譯失敗
this->status = COMPLIE_ERROR;
}
this->complieError = buf;
}else {
//查看ppid都是同一個(gè)
printf("ppid:%d\n",getppid());
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
dup2(fd[1], STDERR_FILENO);
this->doCompile();
close(fd[1]);
exit(0);
}
}
如何解決
既然找到問題了秸歧,那么解決也不是很難了厨姚,只需要使用函數(shù)waitpid指定要回收的進(jìn)程id就行。
雖然修改一個(gè)bug很簡(jiǎn)單键菱,但是發(fā)現(xiàn)問題所在才是需要耐心和能力的遣蚀。