Is blocking or volatility required when worker threads are uncompetitively written to local variables or class variables?

In the case below, when there is no competition for records between workflows, do you need locks or volatile requirements? Any difference in response if “Peek” access is not required in “G”.

class A { Object _o; // need volatile (position A)? Int _i; // need volatile (position B)? Method() { Object o; Int i; Task [] task = new Task[2] { Task.Factory.StartNew(() => { _o = f1(); // use lock() (position C)? o = f2(); // use lock() (position D)? } Task.Factory.StartNew(() => { _i = g1(); // use lock() (position E)? i = g2(); // use lock() (position F)? } } // "Peek" at _o, _i, o, i (position G)? Task.WaitAll(tasks); // Use _o, _i, o, i (position H)? } 
+4
source share
3 answers

Writes link types (i.e. Object ) and types of word size values ​​(i.e. int in a 32-bit system) are atomic. This means that when you look at the values ​​(position 6), you can be sure that you will either get the old value or the new value, but not something else (if you have a type such as a large structure that it can spliced, and you can read the meaning when it was halfway written). You do not need lock or volatile if you are ready to accept the potential risk of reading obsolete values.

Please note that since memory protection is not introduced at this moment (a lock or using volatile and add one), it is possible that the variable was updated in another thread, but the current thread is not enforcing these changes; it can read the "obsolete" value (potentially) some time after changing it in another thread. Using volatile ensures that the current thread is more likely to observe changes in the variable.

You can be sure that after calling WaitAll you will have the corresponding value, even without lock or volatile .

Also note that although you can be sure that the link to the reference type is written atomically, your program makes no guarantees regarding the observed order of any changes to the actual object referenced by the link. Even if, from the point of view of the background thread, the object is initialized before it is assigned to the instance field, this may not happen in that order. Thus, another thread can observe the record of the reference object, but then follow this link and find the object in the initialized or partially initialized state. The introduction of a memory barrier (i.e., using the variable volatile can potentially allow you to avoid performing such reorders, thereby ensuring that this does not happen. That is why it is better to simply not do this in the first place , and simply to let the two tasks return results that they generate, not manipulate, a private variable.

WaitAll will introduce a memory barrier, in addition to the fact that both tasks will actually be completed, which means that you know that the variables are relevant and do not have old obsolete values.

+5
source

It’s safe to not do this in the first place. Do not write the value in one stream and read the value in another stream first. Create Task<object> and Task<int> that return values ​​to the stream they need, instead of creating tasks that change the variables in the threads.

If you are damn tuned to write to variables in streams, you need to guarantee two things. Firstly, that jitter does not choose an optimization that will make reading and writing move in time, and secondly, that a memory barrier has been introduced. The memory limit limits the processor from moving read and write in time in certain ways.

As Brian Gideon notes in his answer, you get a WaitAll memory barrier, but I don’t remember if this is a documented guarantee or just an implementation detail.

As I said, I would not do this in the first place. If I were forced to, I would at least make the variables that I wrote so that they are marked as unstable.

+7
source

At position G, you can observe the values _o and _i can save their initialized values ​​null and 0, respectively, or they can contain the values ​​recorded by the tasks. It is unpredictable in this position.

However, in position H, you cause the problem in two different ways. First, you ensured that both tasks were completed, and thus the recordings were completed. Secondly, Task.WaitAll will create a memory barrier that ensures that the main thread will observe the new values ​​published by the tasks.

Thus, in this particular example, the lock or memory generator ( volatile ) is explicitly prohibited.

+1
source

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


All Articles