it-swarm.com.de

Ist es sicher, innerhalb eines Gewindes zu gabeln?

Lassen Sie mich erklären: Ich habe bereits eine Anwendung unter Linux entwickelt, die eine externe Binärdatei abfragt und ausführt und darauf wartet, dass sie beendet wird. Die Ergebnisse werden von shm-Dateien übermittelt, die für den fork + -Prozess spezifisch sind. Der gesamte Code ist in einer Klasse gekapselt.

Jetzt denke ich darüber nach, den Prozess zu vertiefen, um die Dinge zu beschleunigen. Wenn viele verschiedene Instanzen von Klassenfunktionen vorhanden sind, wird die Binärdatei gleichzeitig ausgeführt (mit verschiedenen Parametern) und die Ergebnisse werden mit ihren eigenen eindeutigen shm-Dateien kommuniziert.

Ist dieser Thread sicher? Wenn ich mich in einem Thread verzweige, gibt es etwas, auf das ich achten muss, abgesehen davon, dass ich sicher bin? Jeder Rat oder jede Hilfe wird sehr geschätzt!

38
Ælex

forking ist auch bei Threads sicher. Sobald Sie sich verzweigen, sind die Threads für jeden Prozess unabhängig. (Das Einfädeln ist also orthogonal zur Gabelung). Wenn die Threads in verschiedenen Prozessen jedoch den gleichen gemeinsam genutzten Speicher für die Kommunikation verwenden, müssen Sie einen Synchronisationsmechanismus entwickeln.

0
Diego Sevilla

Das Problem ist, dass gork () nur den aufrufenden Thread kopiert und alle in untergeordneten Threads enthaltenen Mutexe für immer in dem verzweigten untergeordneten Element gesperrt sind. Die pthread-Lösung waren die pthread_atfork()-Handler. Die Idee war, dass Sie 3 Handler registrieren können: einen Prefork, einen übergeordneten und einen untergeordneten Handler. Wenn fork() geschieht, wird prefork vor der Abzweigung aufgerufen und erwartet, dass alle Anwendungsmutexe abgerufen werden. Sowohl Eltern als auch Kind müssen alle Mutexe in Eltern- bzw. Kindprozessen freigeben. 

Dies ist jedoch nicht das Ende der Geschichte! Bibliotheken rufen pthread_atfork auf, um Handler für bibliotheksspezifische Mutexe zu registrieren. Libc macht dies beispielsweise. Das ist eine gute Sache: Die Anwendung kann nicht über die von Fremdanbieter-Bibliotheken gehaltenen Mutexe Bescheid wissen. Daher muss jede Bibliothek pthread_atfork aufrufen, um sicherzustellen, dass ihre eigenen Mutexe im Fall einer fork() bereinigt werden. 

Das Problem ist, dass die Reihenfolge, in der pthread_atfork-Handler für nicht verknüpfte Bibliotheken aufgerufen werden, undefiniert ist (dies hängt von der Reihenfolge ab, in der die Bibliotheken vom Programm geladen werden). Dies bedeutet also, dass technisch gesehen ein Deadlock innerhalb eines Prefork-Handlers aufgrund einer Race-Bedingung auftreten kann. 

Betrachten Sie zum Beispiel diese Reihenfolge: 

  1. Thread T1-Aufrufe fork()
  2. prefork-Handler für libc erhalten in T1
  3. Als Nächstes erhält eine Drittanbieter-Bibliothek A in Thread T2 einen eigenen Mutex-AM und führt dann einen libc-Aufruf aus, der einen Mutex erfordert. Dies blockiert, da libc-Mutexe von T1 gehalten werden.
  4. Der Thread T1 führt einen Prefork-Handler für die Bibliothek A aus, der das Warten auf AM, das von T2 gehalten wird, blockiert.

Es gibt Ihren Deadlock, der nicht mit Ihren eigenen Mutexen oder Ihrem Code zusammenhängt. 

Dies geschah tatsächlich bei einem Projekt, an dem ich einmal gearbeitet habe. Der Rat, den ich damals gefunden hatte, war, Gabel oder Fäden zu wählen, aber nicht beide. Aber für manche Anwendungen ist das wahrscheinlich nicht praktikabel.

51
Kevin

Es ist sicher, in einem Multithread-Programm zu verzweigen, solange Sie sehr auf den Code zwischen Fork und Exec achten. In diesem Bereich können Sie nur wiederkehrende (asynchron-sichere) Systemaufrufe durchführen. Theoretisch dürfen Sie dort nicht mallocieren oder freigeben, obwohl in der Praxis der standardmäßige Linux-Zuweiser sicher ist und sich Linux-Bibliotheken darauf verlassen. Das Endergebnis ist, dass Sie must den Standard-Allokator verwenden.

9
Igor Nazarenko

Während Sie die NPTL pthreads(7) -Unterstützung von Linux für Ihr Programm verwenden können , passen Threads auf Unix-Systeme ungeschickt, wie Sie mit Ihrer entdeckt haben fork(2) Frage.

Da fork(2) auf modernen Systemen eine sehr kostengünstige Operation ist, können Sie es besser machen, nur fork(2) Ihren Prozess auszuführen, wenn Sie mehr haben Handhabung durchzuführen. Es hängt davon ab, wie viele Daten Sie verschieben möchten. Die Share-Nothing-Philosophie von forked-Prozessen ist gut, um Fehler bei gemeinsam genutzten Daten zu reduzieren, bedeutet jedoch, dass Sie entweder Pipes erstellen müssen um Daten zwischen Prozessen zu verschieben oder gemeinsam genutzten Speicher zu verwenden (shmget(2) oder shm_open(3)).

Wenn Sie sich jedoch für die Verwendung von Threading entscheiden, können Sie einen neuen Prozess mit den folgenden Hinweisen aus der Manpage fork(2)fork(2) erstellen:

   *  The child process is created with a single thread — the
      one that called fork().  The entire virtual address space
      of the parent is replicated in the child, including the
      states of mutexes, condition variables, and other pthreads
      objects; the use of pthread_atfork(3) may be helpful for
      dealing with problems that this can cause.
6
sarnold

In der Morgendämmerung der Zeit nannten wir Threads "leichtgewichtige Prozesse", da sie zwar ähnlich wie Prozesse agieren, aber nicht identisch sind. Der größte Unterschied besteht darin, dass Threads per Definition in demselben Adressraum eines Prozesses leben. Dies hat Vorteile: Der Wechsel von Thread zu Thread ist schnell, sie teilen sich inhärent Speicher, so dass die Kommunikation zwischen Threads schnell ist und das Erstellen und Entfernen von Threads schnell ist.

Der Unterschied liegt hier bei "Schwergewichtsverfahren", bei denen es sich um komplette Adressräume handelt. Ein neuer Heavyweight-Prozess wird mit fork (2) erstellt. Als virtueller Speicher in die UNIX-Welt kam, wurde dies mit vfork (2) und einigen anderen erweitert.

A Fork (2) kopiert den gesamten Adressraum des Prozesses einschließlich aller Register und setzt diesen Prozess unter die Kontrolle des Betriebssystem-Schedulers; Wenn der Scheduler das nächste Mal vorbeikommt, greift der Anweisungszähler bei der nächsten Anweisung auf - der verzweigte untergeordnete Prozess ist ein Klon des übergeordneten Elements. (Wenn Sie ein anderes Programm ausführen möchten, zum Beispiel, weil Sie eine Shell schreiben, folgen Sie der Gabelung mit einem exec (2) - Aufruf, der diesen neuen Adressraum mit einem neuen Programm lädt und das vorhandene ersetzt geklont.)

Grundsätzlich ist Ihre Antwort in dieser Erklärung verankert: Wenn Sie einen Prozess mit vielen haben LWPs Threads und Sie verzweigen den Prozess, Sie haben zwei unabhängige Prozesse mit vielen Threads, die gleichzeitig ausgeführt werden.

Dieser Trick ist sogar nützlich: In vielen Programmen gibt es einen übergeordneten Prozess, der möglicherweise viele Threads enthält, von denen einige neue untergeordnete Prozesse kreuzen. (Ein HTTP-Server könnte beispielsweise Folgendes tun: Jede Verbindung zu Port 80 wird von einem Thread verarbeitet, und dann könnte ein untergeordneter Prozess für ein CGI-Programm wie ein CGI-Programm ausgeführt werden; exec (2) würde dann aufgerufen werden Führen Sie das CGI-Programm anstelle des übergeordneten Prozesses close aus.)

3
Charlie Martin

Vorausgesetzt, Sie rufen entweder exec oder _exit im verzweigten untergeordneten Prozess an, sind in der Praxis in Ordnung.

Möglicherweise möchten Sie stattdessen posix_spawn () verwenden, was wahrscheinlich das Richtige tut.

1
MarkR

Wenn Sie den Unix-Systemaufruf 'fork ()' verwenden, verwenden Sie technisch gesehen keine Threads. Sie verwenden Prozesse. 

Solange für jeden Prozess andere Dateien verwendet werden, sollte kein Problem auftreten. 

0
Kevin