Pourquoi un programme C multithread est-il forcé sur un seul processeur sous Mac OS X lorsque system () est utilisé dans un thread?

J’ai rencontré une différence étrange dans le comportement d’un programme utilisant pthreads entre Linux et Mac OS X.

Considérez le programme suivant qui peut être compilé avec “gcc -pthread -o threadtest threadtest.c”:

#include  #include  #include  static void *worker(void *t) { int i = *(int *)t; printf("Thread %d started\n", i); system("sleep 1"); printf("Thread %d ends\n", i); return (void *) 0; } int main() { #define N_WORKERS 4 pthread_t workers[N_WORKERS]; int args[N_WORKERS]; int i; for (i = 0; i < N_WORKERS; ++i) { args[i] = i; pthread_create(&workers[i], NULL, worker, args + i); } for (i = 0; i < N_WORKERS; ++i) { pthread_join(workers[i], NULL); } return 0; } 

L’exécution du fichier exécutable résultant sur une machine Mac OS X 4-core entraîne le comportement suivant:

 $ time ./threadtest Thread 0 started Thread 2 started Thread 1 started Thread 3 started Thread 0 ends Thread 1 ends Thread 2 ends Thread 3 ends real 0m4.030s user 0m0.006s sys 0m0.008s 

Notez que le nombre de cœurs réels n’est probablement même pas pertinent, car le temps est simplement passé dans la commande shell “sleep 1” sans aucun calcul. Il est également évident que les threads sont démarrés en parallèle lorsque les messages “Thread … started” apparaissent instantanément après le démarrage du programme.

L’exécution du même programme de test sur une machine Linux donne le résultat attendu:

 $ time ./threadtest Thread 0 started Thread 3 started Thread 1 started Thread 2 started Thread 1 ends Thread 2 ends Thread 0 ends Thread 3 ends real 0m1.010s user 0m0.008s sys 0m0.013s 

Quatre processus sont lancés en parallèle, chacun dormant une seconde, ce qui prend environ une seconde.

Si je mets des calculs réels dans la fonction worker () et supprime l’appel system (), je vois également l’accélération attendue sous Mac OS X.

La question est donc de savoir pourquoi l’utilisation de l’appel system () dans un thread sérialise efficacement l’exécution des threads sur Mac OS X, et comment peut-on empêcher cela?

@BasileStarynkevitch et @null ont souligné qu’un mutex global dans l’implémentation system () de la bibliothèque C de Mac OS X pouvait être responsable du comportement observé. @null a fourni une référence au fichier source potentiel de l’implémentation system (), où ces opérations sont contenues:

 #if __DARWIN_UNIX03 pthread_mutex_lock(&__systemfn_mutex); #endif /* __DARWIN_UNIX03 */ #if __DARWIN_UNIX03 pthread_mutex_unlock(&__systemfn_mutex); #endif /* __DARWIN_UNIX03 */ 

En démontant la fonction system () dans lldb, j’ai vérifié que ces appels sont effectivement présents dans le code compilé.

La solution consiste à remplacer l’utilisation de la fonction de bibliothèque system () C par une combinaison des appels système fork () / execve () / waitpid (). Une preuve de concept rapide pour la modification de la fonction worker () dans l’exemple d’origine:

 static void *worker(void *t) { static const char shell[] = "/bin/sh"; static const char * const args[] = { shell, "-c", "sleep 1", NULL }; static const char * const env[] = { NULL }; pid_t pid; int i = *(int *)t; printf("Thread %d started\n", i); pid = fork(); if (pid == 0) { execve(shell, (char **) args, (char **) env); } waitpid(pid, NULL, 0); printf("Thread %d ends\n", i); return (void *) 0; } 

Avec cette modification, le programme de test s’exécute maintenant en une seconde environ sous Mac OS X.