GNU/Linux >> Tutoriels Linux >  >> Linux

Pthreads et Vfork ?

J'essaie de vérifier ce qui arrive réellement aux pthreads pendant que l'un d'eux exécute vfork.
La spécification indique que le "thread of control" parent est "suspendu" jusqu'à ce que le processus enfant appelle exec* ou _exit.
Si j'ai bien compris, le consensus est que cela signifie que l'ensemble du processus parent (c'est-à-dire :avec tous ses pthreads) est suspendu.
J'aimerais le confirmer à l'aide d'une expérience.
Jusqu'à présent, j'ai effectué plusieurs expériences, qui suggèrent toutes que d'autres pthreads sont en cours d'exécution. Comme je n'ai aucune expérience Linux, je soupçonne que mon interprétation de ces expériences est erronée, et apprendre la véritable interprétation de ces résultats pourrait aider à éviter d'autres idées fausses dans ma vie.
Voici donc les expériences que j'ai faites :

Expérience 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;
    }
  }
}

Il existe 44 pthreads, qui exécutent tous vfork et exec dans l'enfant.
Chaque processus enfant effectue deux opérations de sortie entre vfork et exec "A" et "B".
La théorie suggère que la sortie devrait indiquer ABABABABABBA…sans imbrication.
Cependant, la sortie est un désordre total :par exemple :

AAAA



BB
B

B

Expérience II

Suspectant que l'utilisation de I/O lib après vfork pourrait être une mauvaise idée, j'ai remplacé la fonction job() par ce qui suit :

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

Cette fois, j'effectue deux boucles telles que la seconde annule les résultats de la première, donc à la fin la table globale t[] doit être de retour à l'état initial (qui, par définition, est composé uniquement de zéros).
Si l'entrée dans le processus enfant gèle les autres pthreads, les rendant incapables d'appeler vfork jusqu'à ce que l'enfant actuel termine les boucles, alors le tableau doit être composé uniquement de zéros à la fin.
Et j'ai confirmé que lorsque j'utilise fork() au lieu de vfork(), le code ci-dessus ne produit aucune sortie.
Cependant, lorsque je change fork() en vfork(), j'obtiens des tonnes d'incohérences signalées à stdout.

Connexe : Boucle dans les fichiers avec des espaces dans les noms ? ?

Expérience III

Une autre expérience est décrite ici https://unix.stackexchange.com/a/163761/88901 - elle impliquait d'appeler sleep, mais en fait, les résultats étaient les mêmes lorsque je l'ai remplacé par un long for boucle.

Réponse acceptée :

La page de manuel Linux pour vork est assez spécifique :

vfork() diffère de fork(2) en ce que le thread appelant est suspendu jusqu'à ce que l'enfant se termine

Ce n'est pas tout le processus, mais bien le thread appelant . Ce comportement n'est pas garanti par POSIX ou d'autres standards, d'autres implémentations peuvent faire des choses différentes (jusqu'à et y compris simplement implémenter vfork avec un simple fork ).

(Rich Felker note également ce comportement dans vfork considéré comme dangereux.)

Utiliser fork dans un programme multi-thread est déjà assez difficile à raisonner, appeler vfork est au moins aussi mauvais. Vos tests sont pleins de comportements indéfinis, vous n'êtes même pas autorisé à appeler une fonction (et encore moins faire des E/S) à l'intérieur du vfork ‘d enfant, sauf pour exec -fonctions de type et _exit (même pas exit , et le retour provoque le chaos).

Voici un exemple adapté du vôtre qui, je crois, est presque sans comportement indéfini en supposant un compilateur/une implémentation qui ne génère pas d'appels de fonction pour les lectures et écritures atomiques sur int s. (Le seul problème est l'écriture dans start après le vfork - ce n'est pas autorisé.) Gestion des erreurs élidé pour le garder court.

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

L'idée est la suivante :les threads normaux attendent (boucle occupée) la variable globale atomique start être 1 avant d'incrémenter un compteur atomique global. Le thread qui fait un vfork définit start à 1 dans l'enfant vfork, puis attend (boucle occupée à nouveau) que les autres threads aient incrémenté le compteur.

Si les autres threads ont été suspendus pendant vfork , aucun progrès ne pourrait jamais être fait :les threads suspendus n'incrémenteraient jamais counter (ils auraient été suspendus avant le start a été défini sur 1 ), de sorte que le thread vforker serait bloqué dans une attente infinie.


Linux
  1. Le Résultat De Ls * , Ls ** Et Ls *** ?

  2. La Différence Entre [[ $a ==Z* ]] Et [ $a ==Z* ] ?

  3. ${!foo} Et Zsh ?

  4. Couper / Grep Et Df -h ?

  5. Grep et queue -f ?

Dépannage et pièges de SELinux

Charger et télécharger

pthreads mutex vs sémaphore

Boost et Autoconf

Git et liens physiques

Rechercher et copier des fichiers