简体   繁体   中英

Is DCL (double-checked locking) thread-safe when implemented by ‘Sequentially Consistent Atomics’ semantics in C++?

Here is a piece of code that is DCL (double-checked locking) implemented by 'Sequentially Consistent Atomics' semantics in C++. The code is as follows:

std :: atomic <Singleton *> Singleton :: m_instance;
std :: mutex Singleton :: m_mutex;

Singleton * Singleton :: getInstance () {
     Singleton * tmp = m_instance.load (); // 3
     if (tmp == nullptr) {
         std :: lock_guard <std :: mutex> lock (m_mutex);
         tmp = m_instance.load ();
         if (tmp == nullptr) {
             tmp = new Singleton; // 1
             m_instance.store (tmp); // 2
         }
     }
     return tmp;
}

I think: '1' includes not only read and write commands, but also call commands, then the call commands may be reordered behind '2'; then '3' may get an unsafe 'tmp' pointer.

So, I want to ask: Is the above code thread-safe?

Note: I know that this answer is true for C and C++. It is not necessarily true for other languages, and I don't know if it applies for Java.

The compiler is basically free to do anything that does not change the external behavior of the program, so yes, they may be reordered.

I guess the question is a bit philosophical. It is not necessary the case that you can say that a certain assembly instruction belongs to a certain line in the C code. Often it is, but often not, and if you enable optimizations it can rearrange things quite a bit.

So in a way the question is kind of meaningless, because when the program is executed, it is not done line by line. One exception is if you compile it with the debug parameter. That will allow you to execute the program line by line. In that case, they will not be reordered.

Note though that as soon as you invoke undefined behavior, the compiler is free to do what it wants. And yes, sometimes a line that invokes undefined behavior cause pretty weird reorganization of the code. For instance, this program:

int main()
{
    int a;
    printf("Hello, World!\n");
    a=1/0; // This causes UB
}

is not guaranteed to print "Hello, World,". even though the line that causes UB comes after the print.

Yes, instructions can be reordered outside of an if or while. But only if doing so would not change the defined behaviour of the program.

For example a computation such as an add, might be performed outside an if, but it's result only stored inside it.

It is worth noting that if you have undefined behaviour in your program, this can cause extremely unexpected behaviour. Including for the effect of the undefined behaviour to happen before it's cause. https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633 has some good examples of the kind of transformations modern compilers can do to undefined behaviour.

Yes, it is safe!

In Is the DCL(double-checked locking) implemented in the following C ++ code thread-safe? i already explained why. There you used acquire/release instead of seq-cst, but the argument remains the same.

If the acquire-load returns null (ie, the singleton is not yet initialized) you acquire the mutex. Inside the mutex the reload can be relaxed since the modifications of m_instance is protected by the mutex anyway, ie, if some other thread has already initialized the singleton, then the mutex-release of that thread must have happend before our mutex-acquire operation, so it is guaranteed that we see the updated m_instance .

If the acquire-load (1) "sees" the value written by the release-store (2), the two operations synchronize-with each other thereby establishing a happens-before relation, so you can safely access the object tmp points to.

The release-store is also protected by the mutex, and it is not possible that a part of the initialization of tmp is reordered with the store. In general one should avoid to argue about possible reorderings. The standard says nothing about if/how operations can be reordered. Instead, it defines the (inter-thread)-happens-before relation. Any reorderings that a compiler may perform are merely a result of applying the rules of the happens-before relations.

If the acquire-load (1) loads the value written by the release-store (2), the two operations synchronize-with each other thereby establishing a happens-before relation, ie, (2) happens-before (3). But since (1) is sequenced-before (2) and the happens-before relation is transitive, it has to be guaranteed that (1) happens-before (3). Thus it is not possible to reorder (1) (or parts of it) with (2).

Yes...

When writing code you are not writing instructions for the CPU, but you write an abstract description that the compiler uses to create an executable. Compilers can and do reorder "instructions" when they see appropriate and when it does not change the observable behavior of the program as defined by the respective language specification.

...and no

When writing code it does not really matter too much that the compiler will apply transformations to it, because those transformations will not change the observable behavior of the program. The resulting program will behave as if the instruction were executed in the order you wrote them down in your code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM