2

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?

Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
BobJao
  • 79
  • 1
  • 6
  • What do you mean by reordered? They stay in the order given in the code, otherwise it would be a total mess. – nvoigt May 08 '20 at 10:48
  • 1
    They might. E.g. Java specifically states that [reordering is allowed](https://docs.oracle.com/javase/specs/jls/se14/html/jls-17.html#jls-17.4) as long as the system can guarantee the effect is the same: *However, compilers are allowed to reorder the instructions in either thread, when this does not affect the execution of that thread in isolation.* – Andreas May 08 '20 at 10:49
  • 1
    The compiler will reorder whatever it can and want, as long as the observed behaviour stays the same – Jeffrey May 08 '20 at 10:49
  • Even if the instructions are not reordered by the compiler/JIT stage, modern CPUs may have out-of-order pipelines that freely reorder whole instructions or uops _so long as_ the observable behaviour is essentially the same. Does that count? Could it possibly matter to you? Why do you care? – Useless May 08 '20 at 10:50
  • Hi. Just a suggestion, I think its better if you asked the whole question in the question section and kept the title as a summary instead of asking half question in each part. – Abhay Aravinda May 08 '20 at 10:51
  • you changed the question to be a completely different question. All answers are now more or less offtopic. And both C and java tag are irrelevant to the question. If you have a question so different from the one you posted originally and you already got answers you could consider to post a different question – 463035818_is_not_an_ai May 08 '20 at 11:22
  • @idclev463035818 Maybe it's a thing to rollback to the "right" state and protect the question? – akuzminykh May 08 '20 at 12:00
  • @akuzminykh note that the question has been closed before OP turned it into a completely different one. I wrote an answer, but I would not object that close votes. The question was unclear before and now it isnt in a much better state, so I would just leave it be – 463035818_is_not_an_ai May 08 '20 at 12:04
  • @idclev463035818 Sorry about this, like this case I will post a new question next time. – BobJao May 08 '20 at 12:24

4 Answers4

4

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.

klutt
  • 30,332
  • 17
  • 55
  • 95
  • 1
    It's important to note for c and c++ that only the defined external behaviour is relevant. If undefined behaviour is present, then the external behaviour can change – user1937198 May 08 '20 at 10:59
  • @user1937198 I was actually writing about that when you commented :) – klutt May 08 '20 at 11:04
3

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.

user1937198
  • 4,987
  • 4
  • 20
  • 31
2

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 (i.e., 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, i.e., 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, i.e., (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).

mpoeter
  • 2,574
  • 1
  • 5
  • 12
1

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.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • 2
    The "... and no" part of course only applies to *valid* programs, i.e. ones that were free of data-race UB in the first place. (In C++ that means using `std::atomic` with at least acquire and release ordering to create any necessary synchronization. But yes, agreed with the first part that C++ isn't a portable assembly language that gets *re*-ordered; the ISO C++ standard defines ordering in terms of "synchronizes with", not in terms of each thread reading/writing a coherent shared memory state. – Peter Cordes May 08 '20 at 11:42
  • @PeterCordes I saw similar comment below other answer and then added ".. as defined by the respective language specification." to be on the safe side. Anyhow after the question edit this answer has little to do with the question and I am considering to delete it (note that the question originally made no mention of threading or similar) – 463035818_is_not_an_ai May 08 '20 at 11:45
  • Yeah IDK, the question is a mess now, but still starts off with the original question based on the mental model that you're correcting. – Peter Cordes May 08 '20 at 11:52