pthreadとvfork

pthreadとvfork

スレッドの1つがvforkを実行しているときにpthreadに何が起こっているのかを確認したいと思います。仕様は、子プロセスがexec *または_exitを呼び出すまで、親の「制御スレッド」が「停止」されることを示しています。私が理解しているように、これは全体の親プロセス(つまり、すべてのpthread)が一時停止されることを意味します。私はこれを実験的に確認したかった。私はこれまでいくつかの実験を行いましたが、すべて異なる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」の間で2つの出力操作を実行します。理論によると、出力は入れ子にせずにABABABABA...でなければなりません。しかし、出力は完全に混乱しています。たとえば、次のようになります。

AAAA



BB
B

B

実験2

私は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;
}

今回は2つのループを実行し、2番目のループは最初のループの結果を元に戻すので、最終的にグローバルテーブルはt[]初期状態(定義上すべて0)で返される必要があります。子プロセスを入力すると、他のpthreadがハングし、現在の子プロセスがループを完了するまでvforkを呼び出すことができなくなる場合、配列は最終的にすべて0になるはずです。 vfork()の代わりにfork()を使用すると、上記のコードで出力が生成されないことがわかりました。ただし、fork()をvfork()に変更すると、標準出力に多くの不一致が報告されます。

実験3

ここでは別の実験について説明します。https://unix.stackexchange.com/a/163761/88901- sleepを呼び出すことに関連していますが、実際に長いループに置き換えると結果は同じですfor

ベストアンサー1

Linuxのマニュアルページvork非常に具体的:

vfork()違いfork(2)は呼び出すことです。ワイヤー子プロセスが終了するまで一時停止します。

それはプロセスではなく、実際の召命です。ワイヤー。この動作はPOSIXまたは他の標準によって保証されず、他の実装では異なる方法で行うことができます(せいぜい通常のvfork実装を使用することを含むfork)。

(Rich Pelkerもここにあります。vforkは危険と見なされます.)

マルチスレッドプログラムで使用することforkについてすでに推論することは困難です。呼び出しもvfork少なくともそれほど悪いです。あなたのテストは未定義の動作でいっぱいであり、-type関数をvfork除いて 'd'サブアイテムから関数を呼び出すことさえ許可されていません(I / Oの実行はもちろん)。 (それは不可能で、返却すると混乱を引き起こします)exec_exitexit

以下はあなたの例を適用したものです。ほぼintコンパイラ/実装がsのアトミック読み書きの関数呼び出しをエクスポートしないと仮定すると、未定義の動作はありません。 (1つの問題は -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進行は行われません。中断されたスレッドは絶対に増加しないため、countervforkerスレッドは無限の忙しい待機状態に閉じ込められます。start1

おすすめ記事