Comment utiliser pthread atfork() et pthread once() pour réinitialiser les mutex dans les processus enfants

nous avons une bibliothèque partagée C++ qui utilise ZeroC Glace bibliothèque pour RPC et à moins que nous arrêtions le fonctionnement de Ice, nous avons observé des processus enfants suspendus à des Mutex aléatoires. Le Ice runtime démarre les threads, a beaucoup de mutex internes et garde les descripteurs de fichiers ouverts aux serveurs.

de plus, nous avons quelques-uns de nos propres Mutex pour protéger notre état interne.

Notre bibliothèque partagée est utilisée par des centaines d'applications internes si nous n'avons pas le contrôle plus quand le processus appelle fork (), donc nous avons besoin d'un moyen pour arrêter la glace en toute sécurité et verrouiller nos mutex pendant que le processus fourche.

Lecture de la norme POSIX sur pthread_atfork () sur la gestion des mutex et l'état interne:

Par ailleurs, certaines bibliothèques pourraient avoir été en mesure de fournir juste une routine enfant qui réinitialise les mutex dans la bibliothèque et tous les États associés à une certaine valeur connue (par exemple, ce qu'il était quand l'image était à l'origine exécuter.) Cette approche n'est pas possible, cependant, parce que les implémentations sont autorisées à fail *_init() et *_destroy() appelle les mutex et les serrures si le mutex ou la serrure est toujours verrouillé. Dans ce cas, la routine de l'enfant n'est pas capable de réinitialiser les mutex et les serrures.

sur Linux, le Ceci test de programme C renvoie EPERM de pthread_mutex_unlock() dans le gestionnaire pthread_atfork () de l'enfant. Linux nécessite d'ajouter _NP à la macro PTHREAD_MUTEX_ERRORCHECK pour qu'il compiler.

ce programme est lié à ce bon thread.

étant donné qu'il n'est pas techniquement sûr ou légal de déverrouiller ou détruire un mutex chez l'enfant, je pense qu'il est préférable d'avoir des pointeurs vers les mutex et ensuite que l'enfant fasse un nouveau pthread_mutex_t sur le tas et laisse les Mutex du parent seul, ayant ainsi une petite fuite de mémoire.

La seule question Est comment réinitialiser l'état de la bibliothèque et je pense à réécouter un pthread_once_t. Peut-être parce que POSIX a un initialiseur pour pthread_once_t qu'il peut être réinitialisé à son état initial.

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

dans l'exemple ci-dessus de l'enfant (), je n'ai réinitialisé le pthread_once_t qu'en faisant une copie d'un pthread_once_t fraîchement initialisé avec PTHREAD_ONCE_INIT. Un nouveau pthread_mutex_t n'est créé que lorsque la fonction de Bibliothèque est invoquée dans le processus enfant.

C'est hacky mais peut-être la meilleure façon de gérer cette déviation des standards. Si l' pthread_once_t contient un mutex alors le système doit avoir un moyen de l'initialiser à partir de son état PTHREAD_ONCE_INIT. S'il contient un pointeur vers un mutex alloué sur le tas, il sera forcé d'en attribuer un nouveau et de définir l'adresse dans le pthread_once_t. J'espère qu'il n'utilise pas l'adresse du pthread_once_t pour quelque chose de spécial qui pourrait faire échouer ça.

Recherche comps.programmation.les threads du groupe pour pthread_atfork ne() montre beaucoup de bonne discussion et combien peu les standards POSIX fournissent vraiment pour résoudre ce problème.

Il y a aussi le problème que l'on ne devrait appeler que les fonctions async-signal-safe des gestionnaires pthread_atfork (), et il apparaît le le plus important est l'enfant handler, où seulement un memcpy() est fait.

cela fonctionne? Est-il une meilleure façon de traiter avec les exigences de notre bibliothèque partagée?

17
demandé sur Blair Zajac 2010-04-12 10:47:17

2 réponses

Félicitations, vous avez trouvé un défaut dans la norme. pthread_atfork est fondamentalement incapable de résoudre le problème qu'il a été créé pour résoudre avec des Mutex, parce que le manipulateur de l'enfant n'est pas autorisé à effectuer des opérations sur eux:

  • il ne peut pas les déverrouiller, parce que l'appelant serait le nouveau fil principal dans le processus enfant nouvellement créé, et ce n'est pas le même fil comme le thread (dans le parent) qui a obtenu la serrure.
  • Il ne peut pas détruisez-les, car ils sont verrouillés.

une solution possible est d'utiliser des sémaphores POSIX à la place des Mutex ici. Un sémaphore n'a pas de propriétaire, donc si le processus parent le verrouille (sem_wait), les processus parent et enfant peuvent se déverrouiller (sem_post) leurs copies respectives sans invoquer aucun comportement non défini.

Comme un joli côté, sem_post est-async-signal-coffre-fort et ainsi définitivement juridique de l'enfant à utiliser.

21
répondu R.. 2011-07-07 03:30:20

je considère que c'est un bug dans les programmes appelant fork(). Dans un processus multi-threadé, le processus enfant ne devrait appeler que des fonctions async-signal-safe. Si un programme veut bifurquer sans exec, il doit le faire avant de créer des threads.

il n'y a pas vraiment de bonne solution pour threaded fork()/pthread_atfork(). Certains morceaux de celui-ci semblent fonctionner, mais ce n'est pas portable et susceptible de briser les versions D'OS.

7
répondu jilles 2010-09-10 23:02:27