El problema es que fork() solo copia el subproceso de llamada, y cualquier mutex retenido en subprocesos secundarios quedará bloqueado para siempre en el subproceso bifurcado. La solución pthread fue el pthread_atfork()
manipuladores La idea era que puede registrar 3 controladores:un prefork, un controlador principal y un controlador secundario. Cuando fork()
sucede que se llama a prefork antes de fork y se espera que obtenga todas las exclusiones mutuas de la aplicación. Tanto el padre como el hijo deben liberar todos los mutex en los procesos padre e hijo, respectivamente.
¡Sin embargo, este no es el final de la historia! Las bibliotecas llaman a pthread_atfork
para registrar controladores para mutex específicos de la biblioteca, por ejemplo, Libc hace esto. Esto es algo bueno:la aplicación no puede conocer los mutex que tienen las bibliotecas de terceros, por lo que cada biblioteca debe llamar a pthread_atfork
para asegurarse de que sus propios mutexes se limpien en caso de un fork()
.
El problema es que el orden que pthread_atfork
Los controladores que se llaman para bibliotecas no relacionadas no están definidos (depende del orden en que el programa carga las bibliotecas). Entonces, esto significa que, técnicamente, puede ocurrir un punto muerto dentro de un controlador de horquilla previa debido a una condición de carrera.
Por ejemplo, considere esta secuencia:
- Subproceso T1 llama
fork()
- Los controladores de prefork de libc se llaman en T1 (por ejemplo, T1 ahora contiene todos los bloqueos de libc)
- A continuación, en Thread T2, una biblioteca A de terceros adquiere su propio mutex AM y luego realiza una llamada libc que requiere un mutex. Esto bloquea, porque los mutex de libc están en manos de T1.
- El subproceso T1 ejecuta el controlador prefork para la biblioteca A, que bloquea la espera para obtener AM, que está en manos de T2.
Ahí está su interbloqueo y no está relacionado con sus propios mutexes o código.
Esto realmente sucedió en un proyecto en el que trabajé una vez. El consejo que encontré en ese momento fue elegir horquilla o hilos, pero no ambos. Pero para algunas aplicaciones eso probablemente no sea práctico.
Es seguro bifurcar un programa multiproceso siempre que sea muy cuidado con el código entre fork y exec. Solo puede realizar llamadas al sistema de reingreso (también conocidas como seguras asíncronas) en ese lapso. En teoría, no se le permite hacer malloc o liberar allí, aunque en la práctica el asignador predeterminado de Linux es seguro, y las bibliotecas de Linux llegaron a depender de él. El resultado final es que debe utilice el asignador predeterminado.