pthread_join - Cómo usar pthread_atfork() y pthread_once() para reinicializar las exclusiones mutuas en procesos secundarios
pthread_create en c linux (2)
Tenemos una biblioteca compartida de C ++ que usa la biblioteca Ice de ZeroC para RPC y, a menos que cerremos el tiempo de ejecución de Ice, hemos observado procesos secundarios que cuelgan de mutex aleatorios. El tiempo de ejecución de Ice inicia subprocesos, tiene muchas exclusiones internas y mantiene los descriptores de archivos abiertos a los servidores.
Además, tenemos algunos de mutexes propios para proteger nuestro estado interno.
Nuestra biblioteca compartida es utilizada por cientos de aplicaciones internas, por lo que no tenemos control sobre cuándo el proceso llama a fork (), por lo que necesitamos una forma de apagar el hielo de manera segura y bloquear nuestros mutex mientras el proceso se bifurca.
Leyendo el estándar POSIX en pthread_atfork() sobre el manejo de exclusión mutua y el estado interno:
Alternativamente, algunas bibliotecas podrían haber podido suministrar solo una rutina secundaria que reinicializa los mutex en la biblioteca y todos los estados asociados a algún valor conocido (por ejemplo, qué era cuando se ejecutó la imagen originalmente). Sin embargo, este enfoque no es posible porque las implementaciones pueden fallar * _init () y * _destroy () requieren mutexes y bloqueos si el mutex o el bloqueo aún están bloqueados. En este caso, la rutina secundaria no puede reinicializar las exclusiones y bloqueos.
En Linux, el programa C de esta prueba devuelve EPERM de pthread_mutex_unlock () en el controlador pthread_atfork () secundario. Linux requiere agregar _NP a la macro PTHREAD_MUTEX_ERRORCHECK para que compile.
Este programa está vinculado desde este buen hilo .
Dado que técnicamente no es seguro o legal desbloquear o destruir un mutex en el niño, creo que es mejor tener punteros a mutexes y luego hacer que el niño haga un nuevo pthread_mutex_t en el montón y dejar los mutexes de los padres solo, por lo tanto tener un pequeña pérdida de memoria.
El único problema es cómo reinicializar el estado de la biblioteca y estoy pensando en reiniciar un pthread_once_t. Tal vez porque POSIX tiene un inicializador para pthread_once_t que puede restablecerse a su estado inicial.
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static pthread_mutex_t *mutex_ptr = 0;
static void
setup_new_mutex()
{
mutex_ptr = malloc(sizeof(*mutex_ptr));
pthread_mutex_init(mutex_ptr, 0);
}
static void
prepare()
{
pthread_mutex_lock(mutex_ptr);
}
static void
parent()
{
pthread_mutex_unlock(mutex_ptr);
}
static void
child()
{
// Reset the once control.
pthread_once_t once = PTHREAD_ONCE_INIT;
memcpy(&once_control, &once, sizeof(once_control));
}
static void
init()
{
setup_new_mutex();
pthread_atfork(&prepare, &parent, &child);
}
int
my_library_call(int arg)
{
pthread_once(&once_control, &init);
pthread_mutex_lock(mutex_ptr);
// Do something here that requires the lock.
int result = 2*arg;
pthread_mutex_unlock(mutex_ptr);
return result;
}
En el ejemplo anterior en el archivo child (), solo reinicio pthread_once_t haciendo una copia de un pthread_once_t nuevo inicializado con PTHREAD_ONCE_INIT. Un nuevo pthread_mutex_t solo se crea cuando se invoca la función de biblioteca en el proceso hijo.
Esto es intrépido, pero tal vez sea la mejor manera de lidiar con esta norma. Si pthread_once_t contiene un mutex, entonces el sistema debe tener una forma de inicializarlo desde su estado PTHREAD_ONCE_INIT. Si contiene un puntero a un mutex asignado en el montón, será forzado a asignar uno nuevo y establecer la dirección en pthread_once_t. Espero que no use la dirección de la pthread_once_t para nada especial que derrote esto.
Buscar en el grupo comp.programming.threads para pthread_atfork () muestra mucha buena discusión y lo poco que realmente proporcionan los estándares POSIX para resolver este problema.
También existe el problema de que solo se deben llamar a funciones async-signal-safe desde los controladores pthread_atfork (), y parece que el más importante es el controlador secundario , donde solo se realiza un memcpy ().
¿Esto funciona? ¿Hay una mejor manera de lidiar con los requisitos de nuestra biblioteca compartida?
Considero esto un error en los programas que llaman fork (). En un proceso de subprocesos múltiples, el proceso hijo debe llamar solo a funciones async-signal-safe. Si un programa quiere bifurcar sin exec, debe hacerlo antes de crear subprocesos.
Realmente no hay una buena solución para la bifurcación () / pthread_atfork (). Algunos fragmentos parecen funcionar, pero esto no es portátil y puede romperse en las versiones del sistema operativo.
Enhorabuena, has encontrado un defecto en la norma. pthread_atfork
es fundamentalmente incapaz de resolver el problema que fue creado para resolver con mutexes, porque el controlador en el hijo no está autorizado a realizar ninguna operación en ellos:
- No puede desbloquearlos, porque la persona que llama sería el nuevo hilo principal en el proceso secundario recién creado, y ese no es el mismo hilo que el hilo (en el padre) que obtuvo el bloqueo.
- No puede destruirlos, porque están encerrados.
- No puede reinicializarlos, porque no han sido destruidos.
Una solución potencial es usar semáforos POSIX en lugar de mutexes aquí. Un semáforo no tiene un propietario, por lo tanto, si el proceso principal lo bloquea ( sem_wait
), tanto el proceso primario como el secundario pueden desbloquear ( sem_post
) sus copias respectivas sin invocar ningún comportamiento indefinido.
Además, sem_post
es async-signal-safe y, por lo tanto, definitivamente legal para que lo use el niño.