Forking semantics in multithreaded programs

There are two ways for programs to achieve concurrency on Unix-like systems: fork(2) and POSIX threads. Programmers tend to use one or the other when writing their programs, but what happens if you try to combine them?

One of the more obscure semantic about fork(2) is described in the POSIX standard:

A process shall be created with a single thread. If a multi-threaded process calls fork(), the new process shall contain a replica of the calling thread and its entire address space, possibly including the states of mutexes and other resources. Consequently, to avoid errors, the child process may only execute async-signal-safe operations until such time as one of the exec functions is called.

In other words, if thread A acquires a lock and thread B calls fork(2), then thread B in the child process may never acquire the lock.

POSIX provides a pthread_atfork(3) function to assist in handling the state of the parent and child processes before and after calling fork(2). What makes mixing these concurrency patterns challenging is not being certain about the state of library functions after forking a process.

For example, to be POSIX-compliant, functions like printf(3) must be thread-safe, this implies the implementation has a lock burried somewhere within the implementation. Additionally, the state of the lock after calling fork(2) is dubious. If a function has any chance of being fork-safe then it will need to check the return value of pthread_mutex_lock(3) and re-initialize the lock when the function returns EOWNERDEAD.

This work is licensed under a Creative Commons License.