当其中一个线程执行vfork时,我试图检查其中一个线程到底发生了什么。规范说父“控制线程”被“挂起”,直到子进程调用exec*或_exit。据我所知,协商一致意见是,它意味着整个父进程(即:它的所有线程)都被挂起。我想用一个实验来证实。到目前为止,我进行了几个实验,所有这些实验都表明其他线程正在运行。由于我没有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个线程,它们都在子线程中执行vfork和exec。每个子进程执行两个输出操作,两个输出操作位于vfork和exec "A“和"B”之间。理论建议输出应该是ABABABABABA...without嵌套。但是,输出是完全混乱的:例如:
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[]应该返回到初始状态(根据定义,它都是零)。如果进入子进程会冻结其他线程,使它们在当前子进程完成循环之前无法调用v叉,那么数组应该是末尾的所有零。并且我确认,当我使用fork()而不是vfork()时,上面的代码不会产生任何输出。但是,当我将fork()改为vfork()时,会向stdout报告大量的不一致之处。
这里还描述了另一个实验,https://unix.stackexchange.com/a/163761/88901 --它涉及调用睡眠,但实际上,当我用一个长for循环替换它时,结果是一样的。
发布于 2014-10-24 10:21:59
用于vork的Linux手册页非常具体:
vfork()与fork(2)的不同之处在于调用线程被挂起,直到子线程终止。
这不是整个过程,而是调用线程。这种行为不受POSIX或其他标准的保证,其他实现可能会做不同的事情(包括简单地用普通的vfork实现fork)。
(Rich还在维福克被认为危险中注意到了这种行为。)
在多线程程序中使用fork已经很难解释了,调用vfork至少也同样糟糕。您的测试中充满了未定义的行为,您甚至不允许在vfork'd子内部调用一个函数(更不用说做I/O了),除了exec-type函数和_exit (甚至不允许调用exit,并且返回会导致混乱)。
下面是您的示例,我认为它几乎没有未定义的行为,假设编译器/实现不输出函数在ints上调用原子读写。(问题之一是在vfork之后写入start --这是不允许的。)错误处理被省略,以保持其简短。
#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在增量全局原子计数器之前成为1。执行vfork的线程将start设置为v叉子节点中的1,然后等待其他线程增加计数器(再次是繁忙循环)。
如果其他线程在vfork期间被挂起,则无法取得任何进展:挂起的线程永远不会增加counter (它们在start设置为1之前就会挂起),因此vforker线程将陷入无限繁忙的等待状态。
https://unix.stackexchange.com/questions/163947
复制相似问题