问题
When a thread safe singleton has to be implemented using C++11 the only correct implementation I know is the following:
// header
class Singleton final {
public:
static Singleton& getInstance();
private:
Singleton() = default;
Singleton(Singleton const&) = delete;
void operator=(Singleton const&) = delete;
};
// implementation:
Singleton& Singleton::getInstance() {
static Singleton instance;
return instance;
}
In his Book "C++ Concurrency in Action" A. Williams writes that since C++11 "the initialization is defined to happen on exactly one thread" and so this "can be used as an alternative to std::call_once" when a single global instance is required. I wounder when the destructor of the Singleton is called when defined as above.
The standard (ISO/IEC 14882:2011) defines as part of §3.6.3 e. g.
Destructors for initialized objects (that is, objects whose lifetime has begun) with static storage duration are called as a result of returning from main and as a result of calling std::exit.
and
Calling the function std::abort() declared in cstdlib terminates the program without executing any destructors and without calling the functions passed to std::atexit() or std::at_quick_exit().
So what happens first on an clean exit (returning from main)? Are all threads stopped before or after the destructors "for initialized objects with static storage duration are called"?
I know that it is a bad idea to use a singleton that is provided by a shared library (which might be unloaded before other parts that might use it). What happens when Singleton::getInstance() is called e. g. from other (detached) threads? Could that lead to undefined behavior or will all threads (detached or not) be terminated/joined before the destructors of static variables are called?
(To be clear: I think singleton is an anti-pattern, but when I have to use it I want to know what kind of bad things could happen.)
回答1:
So what happens first on an clean
exit
(returning frommain
)? Are all threads stopped before or after the destructors "for initialized objects with static storage duration are called"?
There is no requirement for std::exit
to stop any threads, neither is for exit
or _Exit
. Partly because abruptly terminating another thread may terminate it at a wrong moment and cause deadlocks in other threads.
The threads are terminated when C++ or C run-time terminates and passes the control flow back to the OS by invoking exit_group (on Linux):
This system call is equivalent to
_exit(2)
except that it terminates not only the calling thread, but all threads in the calling process's thread group. This system call does not return.
That means that the destructors of global objects run in parallel with other existing threads in your process. You must terminate all other threads explicitly and in cooperative fashion before calling std::exit
or returning from main
.
回答2:
The global static and singleton function static destructors are called in reverse order when the last thread exits user code, so there is no chance of multithreading issues. It is possible for the main thread to exit and leave other threads running. It is only when all those threads die that the program really shuts down.
Singletons are great to avoid the issue that there are no guarantees on the order statics are constructed, and if one static depends on the content of another during construction, then behaviour is undefined. With singletons your statics are effectively created on demand.
One issue you do have to be aware of is the phoenix singleton during shutdown. The singleton statics are destroyed in the reverse order to their finishing their construction, but another static/singleton(A) object that was constructed earlier, and didn't call on the singleton(B) during construction might call on the singleton(B) during its own(A) destruction, after the singleton(B) has been destructed.
There is no actual mechanism to cope with this, so std::string uses a reference count and does not rely on the singleton destructor.
Depending on the particular singleton, you might be able to leave the static data in a recognisable phoenix state, so it can either resurrect itself, or disable itself.
For example, a global mutex could be wrapped so that it actually does not lock after it has been phoenixed. There is only one thread, so who cares? A debug logger could temporarily reopen the log file in append mode if phoenixed, perhaps emitting a message to say it is being called too late. Note that these late created objects will never be auto-destructed.
来源:https://stackoverflow.com/questions/49439614/portable-c-singleton-when-is-the-destructor-called