C# Multithreading Series (6)

Synchronize

  When multiple threads share some data, we need to use synchronization techniques to ensure that only one thread accesses and changes the shared state at a time. Note that synchronization issues are related to contention and deadlocks.

example:

static int idx = 0;
static void Add()
{
    for (int i = 0; i < 50000; i++)
    {
        idx++;
    }
}
static void Main()
{
    const int SIZE = 40;
    Task[] arr = new Task[SIZE];
    while (true)
    {
        for (int i = 0; i < SIZE; i++)
        {
            arr[i] = new Task(Add);
            arr[i].Start();          // Start multiple threads 
        }

        for (int i = 0; i < SIZE; i++)
        {
            arr[i].Wait();           // Wait for the thread to finish 
        }

        Console.WriteLine(idx);
        Thread.Sleep(500);
        idx = 0 ; //   reset data, run again 
    }
}

 

result:

1717634
1652989
1444839
1272385
1558097
1297459
1968232
2000000

Obviously, not what we want, we expect the result to be 2000000 for each run. This is because idx++ is not thread-safe, its operations consist of fetching a value from memory, incrementing the value by 1, and storing it back in memory. These operations may be interrupted by the thread scheduler.

In this case, we need some synchronization method to solve the problem.

  • The lock keyword marks a block of statements as a critical section by acquiring the mutex lock on the given object, executing the statement, and releasing the lock. Enter is called at the beginning of  the block and Exit is called at the end of the block . This ensures that when one thread is in a critical section of code, another thread cannot enter that critical section. If another thread tries to enter the locked code, it will wait (i.e. block) until the object is released. A thread, when blocked, does not occupy CPU resources.
static object locker = new object();
static void Add()
{
    for (int i = 0; i < 50000; i++)
    {
        lock (locker)
            idx++;
    }
}

 

  • The Interlocked class is used to make simple statements of variables atomic (the smallest unit of execution, not interrupted halfway through), providing methods to increment, decrement, swap, and read values ​​in a thread-safe manner.
For the above example, replace idx++ with Interlocked.Increment( ref idx);
  •  The Monitor class is a pure class that implements the lock mechanism, and the lock statement is parsed by the compiler to use the Monitor class.
lock(obj)
{
    //synchronized region for obj
}

equivalent to

Monitor.Enter(obj);
try
{
    //synchornized region for obj
}
finally
{
    Monitor.Exit(obj)
}

Use TryEnter to add timeout

 1 object obj = new object();
 2 Task.Run(()=>{
 3     lock(obj)
 4     {
 5         Console.WriteLine("lock obj");
 6         Thread.Sleep(3000);
 7     }
 8 });
 9 bool b = Monitor.TryEnter(obj, 2000);
10 if (b)
11 {
12     try
13     {
14         Console.WriteLine("monitor enter.");
15     }
16     finally
17     {
18         Monitor.Exit(obj);
19     }
20 }
21 else
22 {
23     Console.WriteLine("monitor enter false.");
24 }
25 
26 Console.ReadKey();

 

Additionally, Monitor also provides the Wait method, which releases the lock on the object and blocks the current thread until it reacquires the lock.

The Pulse method is provided for notifying the changes of the lock object state of the threads in the waiting queue; PulseAll notifies all the waiting threads of the change of the object state.

  •  SpinLock spin lock, if the system overhead based on object locking (Monitor) is too high due to garbage collection, the SpinLock structure can be used. The SpinLock structure is useful if there are a large number of locks (for example, one lock per node in the list), and the lock time is always very short. You should avoid using multiple SpinLock structures, and don't call anything that might block. SpinLock should only be used for you, as it is determined that doing so can improve the performance of your application.  Another point to note that  SpinLock is a value type , in order to improve performance.  For this reason, you must be very careful to avoid accidentally duplicating  SpinLock instances, as both instances (original and copy) will be completely independent of each other, which may lead to misbehaving applications.  If  the SpinLock must be passed around the instance, it should be passed by reference rather than by value.

    Please do not store  instances of SpinLock in read-only fields.

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324901232&siteId=291194637