Le problème est que fork() ne copie que le thread appelant, et tous les mutex contenus dans les threads enfants seront définitivement verrouillés dans l'enfant forké. La solution pthread était le pthread_atfork()
manutentionnaires. L'idée était que vous pouvez enregistrer 3 gestionnaires :un préfork, un gestionnaire parent et un gestionnaire enfant. Quand fork()
se produit prefork est appelé avant fork et devrait obtenir tous les mutex d'application. Le parent et l'enfant doivent libérer tous les mutex dans les processus parent et enfant respectivement.
Ce n'est pourtant pas la fin de l'histoire ! Les bibliothèques appellent pthread_atfork
pour enregistrer des gestionnaires pour les mutex spécifiques à la bibliothèque, par exemple Libc le fait. C'est une bonne chose :l'application ne peut pas connaître les mutex détenus par les bibliothèques tierces, donc chaque bibliothèque doit appeler pthread_atfork
pour s'assurer que ses propres mutex sont nettoyés en cas de fork()
.
Le problème est que la commande pthread_atfork
les gestionnaires sont appelés pour des bibliothèques non liées n'est pas défini (cela dépend de l'ordre dans lequel les bibliothèques sont chargées par le programme). Cela signifie donc que techniquement, un blocage peut se produire à l'intérieur d'un gestionnaire de préfork en raison d'une condition de concurrence.
Par exemple, considérez cette séquence :
- Le thread T1 appelle
fork()
- les gestionnaires de préfork libc sont appelés dans T1 (par exemple, T1 détient maintenant tous les verrous libc)
- Ensuite, dans Thread T2, une bibliothèque tierce A acquiert son propre mutex AM, puis effectue un appel libc qui nécessite un mutex. Cela bloque, car les mutex libc sont détenus par T1.
- Le thread T1 exécute le gestionnaire de préfork pour la bibliothèque A, qui bloque l'attente d'obtenir AM, qui est détenu par T2.
Il y a votre blocage et ce n'est pas lié à vos propres mutex ou code.
Cela s'est produit sur un projet sur lequel j'ai déjà travaillé. Le conseil que j'avais trouvé à l'époque était de choisir fourche ou fils mais pas les deux. Mais pour certaines applications, ce n'est probablement pas pratique.
Il est sûr de bifurquer dans un programme multithread tant que vous êtes très attention au code entre fork et exec. Vous ne pouvez effectuer que des appels système réentrants (c'est-à-dire asynchrones sécurisés) dans cette plage. En théorie, vous n'êtes pas autorisé à malloc ou à libérer là-bas, bien qu'en pratique l'allocateur Linux par défaut soit sûr, et les bibliothèques Linux en sont venues à s'y fier. Le résultat final est que vous devez utiliser l'allocateur par défaut.