Can a read statement after an uncommitted lock statement be moved before locking?

This question is a continuation of the comments in this thread.

Suppose we have the following code:

// (1) lock (padlock) { // (2) } var value = nonVolatileField; // (3) 

Also, suppose that no command in (2) affects nonVolatileField and vice versa.

Can the read command (3) be reordered so that it ends before the lock statement (1) or inside it (2) ?

As far as I can tell, nothing in the C # Specification (§3.10) and the CLI Specification (§I.12.6.5) prohibits such reordering.

Please note that this is not the same question as this one . Here I ask specifically for reading instructions, because, as I understand it, they are not considered side effects and have weaker guarantees.

+6
source share
2 answers

I believe this is partially guaranteed by the CLI specification, although it is not as clear as it could be. From clause I.12.6.5:

Acquiring a lock ( System.Threading.Monitor.Enter or entering a synchronized method) involves performing an unstable read operation and releasing the lock ( System.Threading.Monitor.Exit or leaving the synchronized method) implicitly performs a volatile write operation. See §I.12.6.7.

Then from I.12.6.7:

Volatile reading has "acquire semantics", which means that reading will be guaranteed before any memory references that occur after a read command in a sequence of CIL commands. Volatile write has "release semantics", which means that a write is guaranteed after every write to the write command in the CIL command sequence.

Therefore, the lock input must prevent (3) from moving to (1). In my opinion, reading from nonVolatileField is still considered a "memory reference". However, reading can still be performed before the volatile write is saved when the lock is completed, so it can be placed in (2).

The C # / CLI memory model is poor. I hope that all of this can be clarified significantly (and probably dragged out to make some "theoretically valid but almost terrible" optimizations invalid).

+3
source

As for .NET, entering the monitor (the lock statement) acquires semantics because it implicitly performs volatile reads and exiting the monitor (end of the lock block) has release semantics because it implicitly performs volatile writes (see §12.6.5 "Locks and flows "to the" Language Infrastructure Separation "(CLI) I).

 volatile bool areWeThereYet = false; // In thread 1 // Accesses, usually writes: create objects, initialize them areWeThereYet = true; // In thread 2 if (areWeThereYet) { // Accesses, usually reads: use created and initialized objects } 

When you write a value before areWeThereYet , all calls to it are executed and do not reorder after an unstable write.

When you read from areWeThereYet , subsequent calls are not reordered until the volatile read is modified.

In this case, when thread 2 notices that areWeThereYet has changed, it has the guarantee that the following calls, as a rule, are read, will observe other calls of the flows, as a rule, writes. Assuming there is no other code with the affected variables.

As with other synchronization primitives in .NET such as SemaphoreSlim , although this is not explicitly documented, it would be useless if they did not have similar semantics. Programs based on them may, in fact, not even work correctly in platforms or hardware architectures with a weaker memory model.


Many people share the idea that Microsoft should provide a strong memory model on such architectures, similar to x86 / amd64, in order to maintain the current code base (compatible with Microsoft and its clients).

I can’t test myself, because I don’t have an ARM device with Microsoft Windows, much less with the .NET Framework for ARM, but at least one MSDN magazine article from Andrew Pardoe, CLR-.NET Development for ARM processors , says:

The CLR is allowed to expose a stronger memory model than the CLI ECMA specification. For example, on x86, the CLR memory model is strong because the processor memory model is strong. The .NET team could make the memory model on ARM as strong as the x86 model, but providing perfect ordering whenever possible could have a significant impact on code execution performance. Weve did targeted work to strengthen the memory model on ARM - in particular, we inserted memory barriers at key points when writing to a managed heap to guarantee type safety, but we did this only with minimal impact on performance. The team conducted several project reviews with experts to ensure that the methods used in the CLR ARM were correct. Moreover, performance tests show that .NET code execution performance is evaluated in the same way as native C ++ code when compared between x86, x64, and ARM.

+2
source

Source: https://habr.com/ru/post/1012929/


All Articles