0

I was testing thread on C#, but a I receive some weird result sometimes, by the logic I believe that the result is "1","2" or nothing, but sometimes the result on console appear "12". There is a bug on C# or there some explanation?

class Program
{
    static int x = 0, y = 0;
    static void Main(string[] args)
    {

        Thread t1, t2;
        t1 = new Thread(function1);
        t2 = new Thread(function2);
        t1.Start();
        t2.Start();
    }
    static void function1()
    {
        x = 1;
        if (y == 0)
        {
            Console.WriteLine("1");
        }
    }
    static void function2()
    {
        y = 1;
        if (x == 0)
        {
            Console.WriteLine("2");
        }
    }
}

enter image description here

D.Silva
  • 21
  • 3
  • 6
    Related: [Why do I need a memory barrier?](https://stackoverflow.com/a/3495287/2791540) – John Wu Apr 15 '21 at 01:10
  • The `x = 1;`/`y = 1;` statements might have been reordered to after the if-statements. To prevent this, use [`volatile`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/volatile). – Sweeper Apr 15 '21 at 01:17
  • 1
    You've set up a scenario that seems the exact scenario to show the problem with threading and accessing shared state. This is a problem in nearly any language that allows shared state/threading. – Keith Nicholas Apr 15 '21 at 01:36
  • 2
    @Sweeper volatile does not help here, it needs a `lock`, `Interlocked.Exchange/Increment` or other proper thread sync mechanism. – tymtam Apr 15 '21 at 02:37
  • For any question of the type "why does my multithreaded program do this?" the answer is probably "it is not thread safe". You need to be aware of the problems with multithreading and how to solve them. Trial and error is not appropriate since it can lead to serious bugs that can be very difficult to reproduce. – JonasH Apr 15 '21 at 06:54

2 Answers2

3

Actually, volatile doesn't help here so I don't think I know what is happening here.


Here is a little part of C# - The C# Memory Model in Theory and Practice:

public class DataInit {
 private int _data = 0;
 private bool _initialized = false;
 void Init() {
   _data = 42;            // Write 1
   _initialized = true;   // Write 2
 }
 void Print() {
   if (_initialized)            // Read 1
     Console.WriteLine(_data);  // Read 2
   else
     Console.WriteLine("Not initialized");
 }
}

Suppose Init and Print are called in parallel (that is, on different threads) on a new instance of DataInit. If you examine the code of Init and Print, it may seem that Print can only output “42” or “Not initialized.” However, Print can also output “0.”

The C# memory model permits reordering of memory operations in a method, as long as the behavior of single-threaded execution doesn’t change. For example, the compiler and the processor are free to reorder the Init method operations as follows:

void Init() {
 _initialized = true;   // Write 2
 _data = 42;            // Write 1
}

This reordering wouldn’t change the behavior of the Init method in a single-threaded program. In a multithreaded program, however, another thread might read _initialized and _data fields after Init has modified one field but not the other, and then the reordering could change the behavior of the program. As a result, the Print method could end up outputting a “0.”

The reordering of Init isn’t the only possible source of trouble in this code sample. Even if the Init writes don’t end up reordered, the reads in the Print method could be transformed:

void Print() {
 int d = _data;     // Read 2
 if (_initialized)  // Read 1
   Console.WriteLine(d);
 else
   Console.WriteLine("Not initialized");
}

Just as with the reordering of writes, this transformation has no effect in a single-threaded program, but might change the behavior of a multithreaded program. And, just like the reordering of writes, the reordering of reads can also result in a 0 printed to the output.

tymtam
  • 31,798
  • 8
  • 86
  • 126
1

You need to use primitive that synchronize across threads.

Here, you can probably use functions from the Interlocked class since you only do simple manipulation of integers.

https://learn.microsoft.com/en-us/dotnet/api/system.threading.interlocked?view=net-5.0

In more complex cases, you need to use mutexes, concurrent containers, background workers, async, Invoke as appropriate. You need to learn available tools and use the appropriate one for what you want to do.

Otherwise, you have to stick to single thread applications or design patterns like async or background worker where you just have to follows the pattern as inter-thread communication is already handled for you.

Phil1970
  • 2,605
  • 2
  • 14
  • 15