Conflict of threads by local variable

why is it that in the following code n does not end with 0, is it a random number with a value of less than 1,000,000 each time, sometimes even a negative number?

static void Main(string[] args) { int n = 0; var up = new Thread(() => { for (int i = 0; i < 1000000; i++) { n++; } }); up.Start(); for (int i = 0; i < 1000000; i++) { n--; } up.Join(); Console.WriteLine(n); Console.ReadLine(); } 

Doesn't work .Join () forcibly terminates the loop to end the loop before WriteLine is called?

I understand that a local variable is actually part of the class behind the scenes (I think it is called closure), however, since the local variable n is actually allocated to the heap, will this affect n not 0 every time?

+6
source share
4 answers

n++ and n-- not guaranteed to be atomic. Each operation has three phases:

  • Read current value from memory
  • Change value (increment / decrement)
  • Write value to memory

Since both of your threads do this several times, and you do not control thread scheduling, you will have situations like this:

  • Thread1: Get n (value = 0)
  • Thread1: Increment (value = 1)
  • Thread2: Get n (value = 0)
  • Thread1: Write n (n == 1)
  • Thread2: Decrement (value = -1)
  • Thread1: Get n (value = 1)
  • Thread2: Write n (n == -1)

And so on.

This is why it is important to always block access to shared data.

- Code:

 static void Main(string[] args) { int n = 0; object lck = new object(); var up = new Thread(() => { for (int i = 0; i < 1000000; i++) { lock (lck) n++; } }); up.Start(); for (int i = 0; i < 1000000; i++) { lock (lck) n--; } up.Join(); Console.WriteLine(n); Console.ReadLine(); } 

- Edit: more about how lock works ...

When you use the lock statement, it tries to get the lock on the object you supply it with - the lck object in my code above. If this object is already locked, the lock statement will cause your code to wait until the lock is released before continuing.

The C # lock statement actually matches the Critical section . In fact, it looks like the following C ++ code:

 // declare and initialize the critical section (analog to 'object lck' in code above) CRITICAL_SECTION lck; InitializeCriticalSection(&lck); // Lock critical section (same as 'lock (lck) { ...code... }') EnterCriticalSection(&lck); __try { // '...code...' goes here n++; } __finally { LeaveCriticalSection(&lck); } 

The C # lock operator abstracts most of this, which means that it’s much harder for us to enter the critical section (get the lock) and forget to leave it.

The important thing is that only your lock object is affected, and only with respect to other threads trying to get a lock on the same object. Nothing prevents you from writing code to modify the lock object itself or access any other object. YOU are responsible for ensuring that your code matches locks and always acquires a lock when writing to a shared object.

Otherwise, you will have a non-deterministic result, as you saw with this code, or what speculators call "undefined behavior". Here is the Be Dragons (in the form of errors that you will encounter endlessly).

+7
source

Yes, up.Join() will ensure that both loops end before WriteLine .

However, it happens that both cycles are executed simultaneously, each of which has its own thread.

Switching between two threads is performed all the time using the operating system, and each run of the program will show a different set of switching times.

You should also know that n-- and n++ are not atomic operations and are actually compiled into 3 sub-operations, for example:

 Take value from memory Increase it by one Put value in memory 

The last part of the puzzle is that switching the context of the stream can occur inside n++ or n-- between any of the above three operations.

That is why the final meaning is not deterministic.

+6
source

If you do not want to use locks, the Interlocked class has atomic versions of the increment and decrement operands.

Change your code to the following and you will always get 0 for an answer.

 static void Main(string[] args) { int n = 0; var up = new Thread(() => { for (int i = 0; i < 1000000; i++) { Interlocked.Increment(ref n); } }); up.Start(); for (int i = 0; i < 1000000; i++) { Interlocked.Decrement(ref n); } up.Join(); Console.WriteLine(n); Console.ReadLine(); } 
+1
source

You need to join streams earlier:

 static void Main(string[] args) { int n = 0; var up = new Thread(() => { for (int i = 0; i < 1000000; i++) { n++; } }); up.Start(); up.Join(); for (int i = 0; i < 1000000; i++) { n--; } Console.WriteLine(n); Console.ReadLine(); } 
-2
source

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


All Articles