I just read this article about the actual reasons behind the current boost::mutex implementation and noticed the following phrase:
Block-scope statics have the additional problem of a potential race
condition on "the first time through", which can lead to the
destructor being run multiple times on popular compilers, which is
undefined behaviour — compilers often use the equivalent of a call to
atexit in order to ensure that destruction is done in the reverse
order of construction, and the initialization race that may cause the
constructor to be run twice may also cause the destructor to be
registered twice
Is it true? Should I really check whether another thread already inside this object's destructor via atomic operations or something like this? Should I do it even in C++11 - C++14? Because as far as I know there's no more "constructor for the same local object with static storage duration can be called simultaneously from several threads" problem since C++11 -- it requires that another threads should wait for the constructor's completion. Am I right?
It looks like this article was written pre C++11, it says amongst other things:
[...] next version of the C++ Standard, scheduled to be released in 2009.[...]
and this was the case pre C++11, it was unspecified what happened in this case since threading was not part of memory model pre C++11.
This changed in C++11 and the draft C++11 standard section 6.7 Declaration statement says (emphasis mine):
The zero-initialization (8.5) of all block-scope variables with static
storage duration (3.7.1) or thread storage duration (3.7.2) is
performed before any other initialization takes place. [...] Otherwise
such a variable is initialized the first time control passes through
its declaration; such a variable is considered initialized upon the
completion of its initialization. If the initialization exits by
throwing an exception, the initialization is not complete, so it will
be tried again the next time control enters the declaration. If
control enters the declaration concurrently while the variable is
being initialized, the concurrent execution shall wait for completion
of the initialization. [...]
Pre C++11 we have to treat the static local variable just like we treat any other critical section. We can find a excellent description of the situation pre C++11 in the post C++ scoped static initialization is not thread-safe, on purpose!.
Related
The C++20 draft, [except.handle]/12 says: (emphasis added)
Exceptions thrown in destructors of objects with thread storage duration or in constructors of namespace-scope objects with thread storage duration are not caught by a function-try-block on the initial function of the thread.
and [except.terminate]/1.10 says (emphasis added)
In some situations exception handling is abandoned for less subtle
error handling techniques. [Note 1: These situations are:
[..]
(1.10) when execution of the initial function of a thread exits via an exception [..]
What is "the initial function of a thread"? Is it the main function? Is it the implementation of std::invoke as specified in [thread.thread.constr]/6? or something else?
I asked someone for that, and the answer was that the initial function of a thread is the first function runned by this thread; and the first function runned by a thread is always the implementation of std::invoke. Is this true?
If that's not true, what is the actual definition of the "initial function of a thread" in the context of the above standard wording?
I asked someone for that, and the answer was that the initial function of a thread is the first function runned by this thread; and the first function runned by a thread is always the implementation of std::invoke. Is this true?
This is not meaningfully true.
The phrase "initial function" has no particular definition in the standard, so it's meant to be taken literally: the function initially run by a thread. And the constructor of std::thread/jthread does say that:
The new thread of execution executes
invoke(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args))...)
with the calls to decay-copy being evaluated in the constructing thread.
However, the "as if" rule still applies. A particular implementation does not specifically have to use std::invoke; it only must behave "as if" it did. And while users are allowed to provide specializations for standard library class templates with user-defined types, they are not allowed to specialize standard library function templates, or overload them outside of very specific circumstances. So there's no way for a user to (validly) interfere with the meaning of std::invoke.
As such, an implementation doesn't specifically have to use std::invoke at all. It could use behavior equivalent to it.
More importantly... it wouldn't matter.
Your program can't change what happens after the call to the function you give the thread. Since you cannot affect that code, any behavior around the "initial function" wording applies to the only thing you can control: the behavior of the function you give it
So if the function you give thread emits an exception, that will necessarily trigger the clause about the behavior of an "initial function of the thread" which doesn't catch an exception. There is no way to cause something below that function call to catch or otherwise interfere with such clauses.
So for all meaningful purposes, "initial function of the thread" is "whatever function you pass that gets called by thread/jthread's constructor".
In invoke(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args))...
Where f is callable and your initial function.
I have this simple function:
bool foo(const std::string& str)
{
static const std::string ky[] = { "KeyWord1", "KeyWord2", "KeyWord3" };
static const std::set<std::string> kySet(ky, ky+ sizeof(ky)/sizeof(ky[0]));
return kySet.find(str) != kySet.end();
}
It basically holds a set of pre-set keywords, and test if a given string is one of the keywords.
I use static because I want only one copy of the pre-set variables.
This will run in a multi-thread environment and on different architectures. However, I was told this is only thread-safe on Linux but will break on AIX and Solaris.
I couldn't understand why it would break?
Quoting from the 03 standard
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1905.pdf
section 6.7
An implementation is permitted to perform early initialization of
other local objects with static storage duration under the same
conditions that an implementation is permitted to statically
initialize an object with static storage duration in namespace scope
(3.6.2). Otherwise such an object is initialized the first time
control passes through its declaration; such an object is considered
initialized upon the completion of its initialization.
There is no mention of threads; and as such you should consider function statics not thread safe unless the function had been called while single threaded.
That can only be true if the compiler does not implement the C++ standard. Otherwise thread-safe dynamic initialization of variables with static storage duration is guaranteed by the standard, see [stmt.dcl].
Dynamic initialization of a block-scope variable with static storage duration or thread storage duration is performed the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration. If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization. [...]
(Emphasis is mine)
Is this singleton thread safe for the pre-C++11 compilers ?
As we know for C++11 it is thread safe.
class Singleton
{
private:
Singleton(){};
public:
static Singleton& instance()
{
static Singleton INSTANCE;
return INSTANCE;
}
};
In C++11 what makes this thread safe is the following from the draft C++11 standard section 6.7 Declaration statement which says (emphasis mine):
The zero-initialization (8.5) of all block-scope variables with static
storage duration (3.7.1) or thread storage duration (3.7.2) is
performed before any other initialization takes place. [...] Otherwise
such a variable is initialized the first time control passes through
its declaration; such a variable is considered initialized upon the
completion of its initialization. If the initialization exits by
throwing an exception, the initialization is not complete, so it will
be tried again the next time control enters the declaration. If
control enters the declaration concurrently while the variable is
being initialized, the concurrent execution shall wait for completion
of the initialization. [...]
while pre C++11 section 6.7 says:
[...]Otherwise such an object is initialized the first time control passes
through its declaration; such an object is considered initialized upon
the completion of its initialization. If the initialization exits by
throwing an exception, the initialization is not complete, so it will
be tried again the next time control enters the declaration.[...]
which does not have the same guarantee that C++11 has and so it would seem pre C++11 it is not specified and therefore you can not count on it. Although this does not prevent implementations from making stronger guarantees.
This make sense since pre C++11 the memory model did not including threading.
I have a function that can be reduced to this:
void f() {
static MyObject o("hello");
DoSomethingWith(o);
}
This function is called across a C API boundary, so like a good boy, I use try to catch any exceptions that are thrown before they cross the boundary and screw things up:
void f() {
try {
static MyObject o("hello");
DoSomethingWith(o);
} catch (const MyObjectException& e) {
Message("Constructor of o failed");
}
}
This function is called the first time and I get the message "Constructor of o failed". However, later, the function is called again, and I get the message again. I get the message as many times as f is called. I am using Visual C++ so this tells me what MSVC++ does, but not what should be done.
My question is, what should happen when the constructor of a static function variable terminates unusually (by throwing, a longjmp out of the constructor, termination of the thread that it's in, etc)? Also what should happen with any other static variables declared before and after it? I would appreciate any relevant quotes from the standard as well.
Section 6.7 ([stmt.dcl]) of the C++11 standard states that
The zero-initialization (8.5) of all block-scope variables with static storage duration (3.7.1) or thread storage duration (3.7.2) is performed before any other initialization takes place. Constant initialization (3.6.2) of a block-scope entity with static storage duration, if applicable, is performed before its block is first entered. An implementation is permitted to perform early initialization of other block-scope variables with static or
thread storage duration under the same conditions that an implementation is permitted to statically initialize a variable with static or thread storage duration in namespace scope (3.6.2). Otherwise such a variable is initialized the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization
is not complete, so it will be tried again the next time control enters the declaration. If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
If control re-enters the declaration recursively while the variable is being
initialized, the behavior is undefined.
Q: what should happen when the constructor of a static function variable terminates unusually [...] ?
A: §6.7 [stmt.dcl] p4
[...] Otherwise such a variable is initialized the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration.
So the initialization of o will be tried again if it exits by throwing an exception. I think the same applies to any kind of abnormal exit from the initialization, though it's not explicitly stated. Brb, looking for more quotes.
Since I couldn't find anything related, I opened a follow-up question.
Q: Also what should happen with any other static variables declared before and after it?
A: Nothing, as long as neither the thread or the whole program terminate.
§3.6.3 [basic.start.term]
Destructors (12.4) for initialized objects (that is, objects whose lifetime (3.8) has begun) with static storage duration are called as a result of returning from main and as a result of calling std::exit (18.5). Destructors for initialized objects with thread storage duration within a given thread are called as a result of returning from the initial function of that thread and as a result of that thread calling std::exit. The completions of the destructors for all initialized objects with thread storage duration within that thread are sequenced before the initiation of the destructors of any object with static storage duration.
§3.7.2 [basic.stc.thread]
A variable with thread storage duration shall be initialized before its first odr-use (3.2) and, if constructed, shall be destroyed on thread exit.
Redesign your program. Static variables will be attempted to be initialized until the initialization succeeds. If that pattern doesn't fit, you should find a better way to express your goal. Perhaps a static unique_ptr that you populate in a controlled environment? If you have a resource that you cannot reliably construct, you have to either relegate the construction into some other context where you can handle the error, or make your function depend only optionally on the resource (e.g. via a null pointer).
Given:
void getBlah() {
static Blah* blah = new Blah();
return blah;
}
In a multi threaded setting, is it possible that new Blah() is called more than once?
Thanks!
The C++ standard makes no guarantee about the thread safety of static initializations - you should treat the static initialization as requiring explicit synchronisation.
The quote Alexander Gessler gives:
If control enters the declaration
concurrently while the object is being
initialized, the concurrent execution
shall wait for completion of the
initialization
is from the C++0x draft, and doesn't reflect the current C++ standard or the behaviour of many C++ compilers.
In the current C++ standard, that passage reads:
If control re-enters the declaration (recursively) while the object is being
initialized, the behaviour is undefined
No. But note that the pointer to Blah is static.
6.7 Declaration statement
4 [...] Otherwise such an object is
initialized the first time control
passes through its declaration; such
an object is considered initialized
upon the completion of its
initialization. If the initialization
exits by throwing an exception, the
initialization is not complete, so it
will be tried again the next time
control enters the declaration
EDIT: This pertains to the C++0x draft.
Quoting the standard (6.7-4):
If control enters the declaration concurrently
while the object is being initialized, the concurrent execution shall wait for completion of the initialization
To my understanding, static initialization like this is thread-safe.