Das Problem ist, dass fork() nur den aufrufenden Thread kopiert und alle in untergeordneten Threads enthaltenen Mutexe für immer im gegabelten untergeordneten Thread gesperrt sind. Die pthread-Lösung war pthread_atfork()
Handler. Die Idee war, dass Sie 3 Handler registrieren können:einen Prefork, einen Eltern-Handler und einen Kind-Handler. Wenn fork()
passiert prefork wird vor fork aufgerufen und soll alle Anwendungs-Mutexe abrufen. Sowohl Parent als auch Child müssen alle Mutexe in Parent- bzw. Child-Prozessen freigeben.
Dies ist jedoch nicht das Ende der Geschichte! Bibliotheken nennen pthread_atfork
Um Handler für bibliotheksspezifische Mutexe zu registrieren, tut dies beispielsweise Libc. Das ist eine gute Sache:Die Anwendung kann unmöglich von den Mutexes wissen, die von Bibliotheken von Drittanbietern gehalten werden, also muss jede Bibliothek pthread_atfork
aufrufen um sicherzustellen, dass seine eigenen Mutexe im Falle eines fork()
bereinigt werden .
Das Problem ist, dass die Bestellung pthread_atfork
Handler für nicht verwandte Bibliotheken aufgerufen werden, ist undefiniert (es hängt von der Reihenfolge ab, in der die Bibliotheken vom Programm geladen werden). Das bedeutet also, dass es technisch gesehen aufgrund einer Rennbedingung zu einem Deadlock innerhalb eines Prefork-Handlers kommen kann.
Betrachten Sie beispielsweise diese Sequenz:
- Thread T1 ruft
fork()
auf - libc-Prefork-Handler werden in T1 aufgerufen (z. B. hält T1 jetzt alle libc-Sperren)
- Als nächstes erwirbt in Thread T2 eine Bibliothek A eines Drittanbieters ihren eigenen Mutex AM und führt dann einen libc-Aufruf durch, der einen Mutex erfordert. Dies blockiert, weil libc Mutexe von T1 gehalten werden.
- Thread T1 führt den Prefork-Handler für Bibliothek A aus, der das Warten auf den Erhalt von AM blockiert, das von T2 gehalten wird.
Es gibt Ihren Deadlock, der nichts mit Ihren eigenen Mutexes oder Code zu tun hat.
Das ist tatsächlich bei einem Projekt passiert, an dem ich einmal gearbeitet habe. Der Rat, den ich damals gefunden hatte, war, Gabel oder Gewinde zu wählen, aber nicht beides. Aber für einige Anwendungen ist das wahrscheinlich nicht praktikabel.
Es ist sicher, ein Multithread-Programm zu verzweigen, solange Sie sehr sind Achten Sie auf den Code zwischen Fork und Exec. Sie können in dieser Spanne nur wiedereintrittsfähige (auch als asynchron sichere) Systemaufrufe tätigen. Theoretisch dürfen Sie dort nicht mallocieren oder freigeben, obwohl in der Praxis der Standard-Linux-Allokator sicher ist und Linux-Bibliotheken sich darauf verlassen. Das Endergebnis ist, dass Sie müssen Verwenden Sie die Standardzuweisung.