GNU/Linux >> Belajar Linux >  >> Linux

Pthreads Dan Vfork?

Saya mencoba memeriksa apa yang sebenarnya terjadi pada pthreads saat salah satu dari mereka menjalankan vfork.
Spesifikasi mengatakan bahwa "utas kontrol" induk "ditangguhkan" hingga proses anak memanggil exec* atau _exit.
Seperti yang saya pahami, konsensusnya adalah bahwa itu berarti seluruh proses induk (yaitu:dengan semua pthreadnya) ditangguhkan.
Saya ingin mengonfirmasinya menggunakan eksperimen.
Sejauh ini saya melakukan beberapa percobaan, yang semuanya menunjukkan bahwa pthreads lain sedang berjalan. Karena saya tidak memiliki pengalaman linux, saya menduga bahwa interpretasi saya terhadap eksperimen ini salah, dan mempelajari interpretasi sebenarnya dari hasil ini dapat membantu menghindari kesalahpahaman lebih lanjut dalam hidup saya.
Jadi, inilah eksperimen yang saya lakukan:

Eksperimen I

#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;
    }
  }
}

Ada 44 pthreads, yang semuanya melakukan vfork, dan exec pada anak.
Setiap proses anak, melakukan dua operasi keluaran antara vfork dan exec “A” dan “B”.
Teori menunjukkan bahwa outputnya harus membaca ABABABABABA…tanpa bersarang.
Namun outputnya berantakan total:misalnya:

AAAA



BB
B

B

Eksperimen II

Mencurigai bahwa menggunakan I/O lib setelah vfork bisa menjadi ide yang buruk, saya telah mengganti fungsi job() dengan yang berikut:

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

Kali ini, saya melakukan dua loop sehingga yang kedua membatalkan hasil dari yang pertama, jadi di akhir tabel global t[] harus kembali ke keadaan awal (yang menurut definisi adalah semua nol).
Jika memasuki proses anak membekukan pthreads lain yang membuat mereka tidak dapat memanggil vfork sampai anak saat ini menyelesaikan loop, maka array harus semua nol di akhirnya.
Dan saya mengkonfirmasi bahwa ketika saya menggunakan fork() alih-alih vfork() maka kode di atas tidak menghasilkan output apa pun.
Namun, ketika saya mengubah fork() menjadi vfork() saya mendapatkan banyak inkonsistensi dilaporkan ke stdout.

Terkait:Perulangan melalui file dengan spasi di nama??

Eksperimen III

Satu eksperimen lagi dijelaskan di sini https://unix.stackexchange.com/a/163761/88901 – ini melibatkan pemanggilan sleep, tetapi sebenarnya hasilnya sama ketika saya menggantinya dengan for lingkaran.

Jawaban yang Diterima:

Halaman manual Linux untuk vork cukup spesifik:

vfork() berbeda dari fork(2) dalam panggilan utas ditangguhkan sampai anak berakhir

Ini bukan keseluruhan proses, tapi memang utas pemanggilan . Perilaku ini tidak dijamin oleh POSIX atau standar lain, implementasi lain dapat melakukan hal yang berbeda (hingga dan termasuk hanya mengimplementasikan vfork dengan garpu biasa ).

(Rich Felker juga mencatat perilaku ini di vfork yang dianggap berbahaya.)

Menggunakan garpu dalam program multi-utas cukup sulit untuk dipikirkan, memanggil vfork setidaknya sama buruknya. Pengujian Anda penuh dengan perilaku yang tidak ditentukan, Anda bahkan tidak diizinkan untuk memanggil fungsi (apalagi melakukan I/O) di dalam vfork 'd anak, kecuali untuk exec -ketik fungsi dan _exit (bahkan tidak keluar , dan kembali menyebabkan kekacauan).

Berikut adalah contoh yang diadaptasi dari Anda yang saya yakini hampir bebas dari perilaku tidak terdefinisi dengan asumsi kompiler/implementasi yang tidak menghasilkan panggilan fungsi untuk membaca dan menulis atom di int s. (Satu-satunya masalah adalah menulis ke start setelah vfork – itu tidak diperbolehkan.) Penanganan kesalahan dihilangkan untuk mempersingkatnya.

#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);
}

Idenya adalah ini:utas normal menunggu (loop sibuk) untuk variabel global atom start menjadi 1 sebelum menambah penghitung atom global. Utas yang melakukan vfork set mulai ke 1 di anak vfork, lalu menunggu (loop sibuk lagi) hingga utas lainnya menambah penghitung.

Jika utas lainnya ditangguhkan selama vfork , tidak ada kemajuan yang dapat dibuat:utas yang ditangguhkan tidak akan pernah menambah counter (mereka akan ditangguhkan sebelum start disetel ke 1 ), sehingga utas vforker akan terjebak dalam penantian sibuk yang tak terbatas.


Linux
  1. Hasil Ls * , Ls ** Dan Ls ***?

  2. Centos 4.8 Dan Glibc 2.5?

  3. Potong / Grep Dan Df -h?

  1. Ikhtisar FTP dan SFTP

  2. Raspberry Pi 4 dan Kali

  3. WSL2 dan Kali

  1. pthreads mutex vs semaphore

  2. Boost dan Autoconf

  3. Perbedaan antara fork(), vfork(), exec() dan clone()