pthreads 和 vfork

pthreads 和 vfork

我试图检查当其中一个线程执行 vfork 时,pthreads 到底发生了什么。规范规定,父“控制线程”将“挂起”,直到子进程调用 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,所有 pthread 都执行 vfork,并在子线程中执行 exec。每个子进程在 vfork 和 exec“A”和“B”之间执行两个输出操作。该理论表明输出应为 ABABABABABA...而无需嵌套。然而输出完全是一团糟:例如:

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- 它涉及调用 sleep,但实际上当我用长循环替换它时,结果是相同的for

答案1

Linux 手册页vork非常具体:

vfork()不同之处fork(2)在于调用线被暂停直到子进程终止

这不是整个过程,而是真正的召唤线。 POSIX 或其他标准不保证此行为,其他实现可能会执行不同的操作(最多包括简单地使用vforkplain实现fork)。

(里奇·费尔克(Rich Felker)也在vfork 被认为是危险的.)

在多线程程序中使用fork已经很难推理了,调用vfork至少同样糟糕。您的测试充满了未定义的行为,您甚至不允许在vfork'd 子级中调用函数(更不用说执行 I/O),除了exec-type 函数和_exit(甚至不可以exit,并且返回会导致混乱)。

这是一个改编自你的例子,我相信是几乎没有未定义的行为,假设编译器/实现不输出对ints 进行原子读取和写入的函数调用。 (一个问题是在 -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 子级中执行设置的1线程,然后等待(再次忙循环)其他线程增加计数器。vforkstart1

如果其他线程在 期间挂起vfork,则永远不会取得任何进展:挂起的线程永远不会增加(它们在设置为counter之前已挂起),因此 vforker 线程将陷入无限繁忙等待。start1

相关内容