After a fork(2) in a multithreaded process returns in the child,
the child should call only async-signal-safe functions (see
signal-safety(7)) until such time as it calls execve(2) to
execute a new program.
And since any process can be multithreaded thanks to shared libraries being able to internally do whatever the hell they want to... yeah. Python, for example, in its implementation of subprocess module, had to shift a lot of work into pre-forked parent (such as disabling the GC and allocating all of the memory for exec()'s arguments), to add explicit "call_setsid" argument to reduce the usage of "preexec_fn", and even then, it still has lovely comments such as
/* We'll be calling back into Python later so we need to do this.
* This call may not be async-signal-safe but neither is calling
* back into Python. The user asked us to use hope as a strategy
* to avoid deadlock... */
Well, hope is usually not a valid thread-safety strategy, but there is really not much else the Python implementation can do.