fork()
« | 02 Dec 2018 | »Unixianer preisen
fork()
für die schnelle und effiziente Art, neue Kind-Prozesse zu erzeugen.
In Windows ist dieses Feature (offiziell) nicht enthalten, was bei der Portierung von Applikationen immer zu Problemen und recht komplexen Workarounds führt.
Während fork()
in manchen finalen Programmen vor allem vor der
Erfindung von Threads
eine großartiges Feature darstellt, so ist - meiner Meinung nach - seine
Funktion für Bibliotheken störend.
fork()
wurde vor C++ und vor der
breiten Nutzung von Threads eingeführt. Wurde ein Prozessraum damals
verdoppelt, gab es also keine parallelen Abläufe, die Seiteneffekte
verursachen konnten.
Ressourcen wurden in C manuell verwaltet, weil
RAII
noch nicht das Licht der Welt erblickt hatte.
Doch was passiert in einer
Multithreading-Umgebung?
fork()
erzeugt hier einen Kindprozess, der nur den einen Thread
weiter ausführt, der fork()
aufgerufen hatte.
Alle anderen Threads existieren einfach nicht mehr.
Obwohl … nicht ganz! Denn alle Ressourcen, die im Elternprozess
von anderen Threads geöffnet wurden, wurden durch fork()
ebenfalls
dupliziert. Sie hängen nun sprichwörtlich in der Luft, da es keinen
Code mehr gibt, der sie nutzt und am Ende freigibt.
Theoretisch hätte man als Programmierer die Möglichkeit über die Funktion
pthread_atfork()
Callbacks zu installieren, die bei einer Prozessgabelung Ressourcen
gezielt freigibt.
Doch dieses Konzept verstößt gegen einen modularen Programmaufbau. Alle
Komponenten müssten ihre Ressourcen global registrieren um im Falle eines
fork()
s eine Aktion ausführen zu können.
Das Problem wird durch den Einsatz von
exec*()
noch verschlimmert, wenn sich so offene Ressourcen auf andere Programme
übertragen, wo sie nicht erkannt und genutzt werden können.
Auch hier gäbe es mit dem Flag
FD_CLOEXEC
theoretisch die Möglichkeit, das automatische Schließen beim fork()
anzustoßen. Doch viele C und C++ Komponenten setzen dieses Flag nicht
und somit haben wir wieder keinen Einfluss darauf.
Und nachdem das Flag oft erst im Nachhinein gesetzt werden kann
(z.B. bei Sockets),
besteht immer die Möglichkeit, dass ein parallel ausgeführtes fork()
unsere Ressource dupliziert, bevor wir sie explizit davon ausnehmen können.
Fazit
Ich bevorzuge Konzepte, die möglichst ohne Seiteneffekte auskommen.
Von daher kann ich die fork()
Strategie als solches nicht gut heißen.
However … mit entsprechendem Mehraufwand lassen sich auch Lösungen finden,
die dieses Seiteneffekte großteils umgehen können.
Für das Schreiben von Plattform-unabhängiger Software ist vom Einsatz von
fork()
abzuraten, weil es auch Plattformen gibt, die die API grundsätzlich
nicht unterstützen.
posix_spawn
stellt hier eine portable Lösung für das Starten neuer Prozesse bereit.
Und für alle Anwendungsfälle, wie man früher Prozesse aufgegabelt hatte, um parallel Ausführung zu erreichen, sollten Threads eingesetzt werden.