0

Traditional C++ was very straightforward and only a library intended to create threads (like pthread) gave rise to other threads.

Modern C++ is much closer to Java with many functions being thread based, with thread pools ready to run asynchronous jobs, etc. It's much more likely that some library, including the standard library, uses threads to compute asynchronously some function, or sets up the infrastructure to do so even if it isn't used.

In that context, is it ever safe to use functions with global impact like fork?

JaMiT
  • 14,422
  • 4
  • 15
  • 31
curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • 1
    It is possible to use fork on any operating system that supports it, regardless of the language. C++ does not define fork -- it's purely part of the OS API. – Chris Dodd Dec 02 '19 at 05:18
  • @ChrisDodd What are the guarantees of the OS API that the programming language will let you fork? IOW, how does all that stuff plays together? – curiousguy Dec 02 '19 at 05:26
  • The OS API defines what it defines. Your question is asking "If I have an apple, can I eat an orange?" -- yes and no; having an apple is irrelevant to the orange. – Chris Dodd Dec 02 '19 at 06:04
  • @ChrisDodd The OS defines a system API, how can I use it from a C++ level? (What constraints? What guarantees?) – curiousguy Dec 02 '19 at 06:08

2 Answers2

3

The answer to this question, like almost everything else in C++, is "it depends".

If we assume there are other threads in the program, and those threads are synchronizing with each other, calling fork is dangerous. This is because, fork does not wait for all threads to be a synchronization point (i.e. mutex release) to fork the process. In the forked process, only the thread that called fork will be present, and the others will have been terminated, possibly in the middle of a critical section. This means any memory shared with other threads, that wasn't a std::atomic<int> or similar, is an undefined state.

If your forked process reads from this memory, or indeed expects the other threads to be running, it is likely not going to work reliably. However, most uses of fork actually have effectively no preconditions on program state. That is because the most common thing to do is to immediately call execv or similar to spawn a subprocess. In this case your entire process is kinda "replaced" by some new process, and all memory from your old process is discarded.

tl;dr - Calling fork may not be safe in multithreaded programs. Sometimes it is safe; like if no threads have spawned yet, or evecv is called immediately. If you are using fork for something else, consider using a thread instead.

See the fork man page and this helpful blog post for the nitty-gritty.

peteigel
  • 363
  • 1
  • 9
  • With third party library, you can't know when threads have been created: they can threads for internal purpose w/o exposing them. I imagine that even the std memory allocator could defer some memory cleanup in a separate thread (but not for running destructors obviously). So better do the bare minimum in forked process and let the parent do the error logging. Fork is crap anyway. – curiousguy Dec 04 '19 at 00:35
2

To add to peteigel's answer, my advice is - if you want to fork, do it very early, before any other threads than the main thread are started.

In general, anything you can do in C, you can do in C++, since C++, especially on Linux with clang or gcc extensions, is pretty darn close to a perfect superset of C. Of course, when there are good portable APIs in std C++, use them. The canonical example is preferring std::thread over pthreads C API.

One caveat is pthread_cancel, which must be avoided on C++ due to exceptions. See e.g. pthread cancel harmful on C++.

Here is another link that explains the problem: pthread_cancel while in destructor

In general, C++ cleanup handling is in general easier and more elegant than C, since RAII is part and parcel of C++ culture, and C does not have destructors.

Erik Alapää
  • 2,585
  • 1
  • 14
  • 25
  • AFAIK asynchronous, non cooperative thread cancellation is always harmful. There is no way to make it safe, unless you are doing purely applicative programming. No mutation no invariant for existing data structure. No heap unless it has no invariant that needs mutex like atomicity protection (maybe a lock free memory allocator). – curiousguy Dec 02 '19 at 20:43