fork()

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.