jfMR
jfMR

Reputation: 24788

Detached threads accessing global or static objects

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.

Upvotes: 6

Views: 1588

Answers (1)

marko
marko

Reputation: 9169

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.

Upvotes: 7

Related Questions