The following text is an excerpt taken from the section 18.2.1† of the book titled The C++ Standard Library: A Tutorial and Reference, 2nd Edition:
Note, however, that the lifetime problem also applies to global and static objects, because when the program exits, the detached thread might still run, which means that it might access global or static objects that are already destroyed or under destruction. Unfortunately, this would result in undefined behavior.
As far as I understand, all detached threads will terminate when main()
ends.
Therefore, I suspect that the reason for this behaviour is that the actual order of destruction of the global and static objects is unspecified with respect to detached threads' termination, i.e., it may happen before, during, or after the detached threads terminate.
Any further clarification on this matter would be appreciated.
†To be more specific: in the subsection under the title Beware of Detached Threads.
But to answer your question, any thread can access any global variable currently in scope. There is no notion of passing variables to a thread. It has a single global variable with a getter and setter function, any thread can call either the getter or setter at any time.
thread::detach Separates the thread of execution from the thread object, allowing execution to continue independently. Any allocated resources will be freed once the thread exits. After calling detach *this no longer owns any thread.
A detached thread will have its resources automatically freed by the system at termination. Thus, you cannot get the thread's termination status, or wait for the thread to terminate by using pthread_join().
A static variable can be either a global or local variable. Both are created by preceding the variable declaration with the keyword static. A local static variable is a variable that can maintain its value from one function call to another and it will exist until the program ends.
Everything that is statically initialised or lazily initialised (for instance, a block-scope static variable whose containing block was entered) gets de-initialised during normal program termination - either by main()
returning or a following a call to exit()
.
The issue isn't so much the sequencing of termination of threads, but rather that there is no effort to stop them at all. Instead, reaping of the (possibly still-running) threads is delegated to the operating system to sort out when the process terminates.
Practically speaking, it would be really difficult for an implementation to force termination of threads - detached or otherwise. Aside from anything else, it's a recipe for unpredictable behaviour as these threads are almost invariably blocked on synchronisation objects or system calls, and hold resources (hello deadlocks!). For another, Posix-Threads doesn't provide an API to do so. It's no surprise that threads need to return from their thread-function to exit.
There is finite period of time between main()
returning and the process terminating in which the run-time performs static de-initialisation (in strict reverse order to that of initialisation) as well as anything registered with atexit()
, during which any extant thread can still be running. In a large program, this time can be significant.
If any of those threads happen to accessing a statically initialised object, this is of course undefined behaviour.
I recently spent quite a bit of time tracking down a series of crashes in a large iOS app featuring much C++.
The crashing code looked much like this, with the crash deep in the bowels of std::set<T>::find(const T&)
bool checkWord(const std::string &w)
{
static std::set<std::string> tags{"foo", "bar"};
return (tags.find(w) != tags.end());
}
Meanwhile, on the main thread, there was a call to exit()
several functions down on the stack.
iOS and macOS apps are heavily multi-threaded using Grand Central Dispatch/libdispatch and it turns out that not only are threads still running after main()
exits, but jobs are being executed from background dispatch queues as well.
I suspect it will be a similar situation on many of other systems.
I found no terribly nice solution to the problem other than avoiding block-scope statics in favour for data that required no initialisation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With