SoFunction
Updated on 2025-03-06

In-depth analysis of C# thread synchronization

Previous articleIt introduces how to start a thread, pass parameters between threads, and the difference between local variables and global shared variables in threads.

This article mainly explains thread synchronization.

If multiple threads access shared data at the same time, thread synchronization must be used to prevent shared data from being corrupted. If multiple threads do not access shared data at the same time, thread synchronization is not required.

There will also be some problems with thread synchronization:

  1. Performance loss. Acquisition, release locks, and thread context switching are all performance-intensive.
  2. Synchronization will queue up threads to be executed.

Several methods of thread synchronization:

block

When the thread calls Sleep, Join, and EndInvoke, the thread is in a blocking state (Sleep blocks the calling thread, and Join and EndInvoke block another thread), and it will immediately exit from the CPU. (Threads in blocking state do not consume CPU)

When a thread switches between a blocking and non-blocking state, it consumes several milliseconds.

//Join
static void Main()
{
 Thread t = new Thread (Go);
  ("The Main method is already running..."); 
 ();
 ();//Blocking Main method  ("The Main method unblocks and continues to run...");
}
 
static void Go()
{
  ("Run Go method on t thread..."); 
}

//Sleep
static void Main()
{
  ("The Main method is already running..."); 
 (3000);//Block the current thread  ("The Main method unblocks and continues to run...");
}
 
 //Task
 static void Main()
{
 Task Task1=(() => { 
  ("task method execution..."); 
  (1000);
  }); 
 ();  
 ();//Block the main thread and wait for the Task1 to complete (); 
}

Lock (lock)

Locking allows multiple threads to call the method at the same time, and other threads are blocked.

Choice of synchronous objects:

  • Using reference types, the value type is boxed when locked, resulting in a new object.
  • Use private modification, and deadlocks are likely to occur when using public. (When using lock(this), lock(typeof(instance)), this class should also be private).
  • string cannot be used as a lock object.
  • Cannot be used in lockawaitKeywords

Does the lock have to be static?

If the locked method is static, the lock must be of static type. This means that the method is locked globally, and no matter how many instances of the class have, it must be queued for execution.

If the locked method is not static, then a static type lock cannot be used, because the locked method belongs to the instance. As long as the instance calls the lock method without causing damage, there is no need for locks between different instances. This lock only locks the method of this instance, not the method of locking all instances.*

class ThreadSafe
{
 private static object _locker = new object();
 
 void Go()
 {
 lock (_locker)
 {
 ......//Static Method, use static locks to ensure that all instances are queued for execution }
 }

private object _locker2=new object();
 void GoTo()
 {
 lock(_locker2)
 //The operation of sharing data is a non-static method, which uses a non-static lock to ensure that the method caller of the same instance is queued for execution. }
}

Synchronous objects can double as objects that it locks

like:

class ThreadSafe
{
 private List <string> _list = new List <string>(); 
 void Test()
 {
 lock (_list)
 {
 _list.Add ("Item 1");
 }
 }
}

Monitors

lockActually it isMonitorsconcise writing.

lock (x) 
{ 
 DoSomething(); 
} 

The two are actually the same.

 obj = ()x; 
(obj); 
try 
{ 
 DoSomething(); 
} 
finally 
{ 
 (obj); 
} 

Mutex

A mutex is a mutex synchronous object, and there is only one thread that can obtain it at the same time. Synchronization of threads at the process level can be achieved.

class Program
 {
 //Instantiate a mutex lock public static Mutex mutex = new Mutex();

 static void Main(string[] args)
 {
  for (int i = 0; i &lt; 3; i++)
  {
  //Calling methods protected by mutex in different threads  Thread test = new Thread(MutexMethod);
  ();
  }
  ();
 }

 public static void MutexMethod()
 {
  ("{0} Request to obtain a mutex lock", );
  ();
  ("{0} Mutex lock has been acquired", ); 
  (1000);
  ("{0} Prepare to release the mutex lock", );
  // Release the mutex lock  ();
  ("{0} Mutex lock has been released", );
 }
 }

Mutex can realize thread synchronization between different processes

Use mutex to implement a function that can only start one application at a time.

 public static class SingleInstance
 {
 private static Mutex m;

 public static bool IsSingleInstance()
 {
  //Does it need to create an application?  Boolean isCreateNew = false;
  try
  {
  m = new Mutex(initiallyOwned: true, name: "SingleInstanceMutex", createdNew: out isCreateNew);
  }
  catch (Exception ex)
  {
  
  }
  return isCreateNew;
 }
 }

The constructor of a mutex with three parameters

  1. initiallyOwned: If initiallyOwned is true, the initial state of the mutex is obtained by the instantiated thread, otherwise the instantiated thread is in the unfetched state.
  2. name: The name of the mutex, there is only one mutex named name in the operating system. If a thread gets the mutex of this name, other threads cannot get the mutex and must wait for the thread to release it to this thread.
  3. createNew: Return false if the mutex of the specified name already exists, otherwise return true.

Signals and handles

lockandmutexThread synchronization can be implemented to ensure that only one thread executes at a time. However, communication between threads cannot be achieved. If threads need to communicate with each other, useAutoResetEvent,ManualResetEvent, communicate with each other through signals. They all have two states, a terminating state and a non-terminating state. The thread can only block when it is in a non-terminated state.

AutoResetEvent:

AutoResetEventThe constructor can pass in a bool type parameter.falseIndicates thatAutoResetEventThe initial state of the object is set to non-terminated. IftrueIdentify the termination status, thenWaitOneThe method will no longer block the thread. However, because this class will automatically modify the termination status to non-termination, it will be called laterWaitOneThe method will be blocked.

WaitOne Method ifAutoResetEventIf the object state is not terminated, the thread calling the method is blocked. You can specify the time, if no signal is obtained, return false

setMethod releases blocked threads. But only one blocked thread can be released at a time.

class ThreadSafe 
{ 
 static AutoResetEvent autoEvent; 

 static void Main() 
 { 
 //Make AutoResetEvent in a non-terminated state autoEvent = new AutoResetEvent(false); 

 ("The main thread runs..."); 
 Thread t = new Thread(DoWork); 
 (); 

 ("Main thread sleeps 1 second..."); 
 (1000); 

 ("The main thread releases the signal..."); 
 (); 
 } 

 static void DoWork() 
 { 
 ("The t thread runs the DoWork method and blocks itself to wait for the main thread signal..."); 
 (); 
 ("The t thread DoWork method obtains the main thread signal and continues to execute..."); 
 } 

} 

Output

The main thread runs...
Main thread sleeps 1 second...
The t thread runs the DoWork method and blocks itself to wait for the main thread signal...
The main thread releases the signal...
t thread DoWork method obtains the main thread signal and continues to execute...

ManualResetEvent

ManualResetEventandAutoResetEventSimilar usage.

AutoResetEventCalledSetAfter the method, the signal will be automatically changed from release (termination) to block (non-termination), and only one thread will get the release signal at a time. andManualResetEventCallingSetAfter the method, the signal will not be automatically changed from release (termination) to block (non-termination), but will keep the signal released, so that multiple blocked threads can be run at a time and can only be called manually.ResetMethod: Change the signal from release (termination) to block (non-termination), and the thread that calls the method again will be blocked again.

public class ThreadSafe
{
 //Create a ManualResetEvent that is in a non-terminated state private static ManualResetEvent mre = new ManualResetEvent(false);

 static void Main()
 {
 for(int i = 0; i &lt;= 2; i++)
 {
  Thread t = new Thread(ThreadProc);
   = "Thread_" + i;
  ();
 }

 (500);
 ("\nThe method of the new thread has been started and is blocked. Call Set to release the blocking thread");

 ();

 (500);
 ("\nWhen ManualResetEvent is terminated, the multithreading of the method is called and will not be blocked.");

 for(int i = 3; i &lt;= 4; i++)
 {
  Thread t = new Thread(ThreadProc);
   = "Thread_" + i;
  ();
 }

 (500);
 ("\nCall the Reset method, ManualResetEvent is in a non-blocking state, and the thread calling the method is blocked again");
 

 ();

 Thread t5 = new Thread(ThreadProc);
  = "Thread_5";
 ();

 (500);
 ("\nCall Set method and release the blocking thread");

 ();
 }


 private static void ThreadProc()
 {
 string name = ;

 (name + " Run and callWaitOne()");

 ();

 (name + " Finish");
 }
}


//Thread_2 runs and calls WaitOne()//Thread_1 runs and calls WaitOne()//Thread_0 runs and calls WaitOne()
//The method of the new thread has been started and is blocked. Call Set to release the blocking thread
//Thread_2 ends//Thread_1 ends//Thread_0 ends
//When ManualResetEvent is in terminated state, the multithreading of the method is called and will not be blocked.
//Thread_3 runs and calls WaitOne()//Thread_4 runs and calls WaitOne()
//Thread_4 ends//Thread_3 ends
///Call the Reset method, ManualResetEvent is in a non-blocking state, and the thread calling the method is blocked again
//Thread_5 runs and calls WaitOne()//Calling the Set method to release the blocking thread//Thread_5 Finish

Interlocked

If a variable is modified by multiple threads, read. Can be usedInterlocked

There is no guarantee that the addition and deletion of a data is atomic, because the operation of data is also divided into steps:

  1. Load the value from the instance variable into the register.
  2. Increase or decrease the value.
  3. Store this value in the instance variable.

InterlockedProvides atomic operations for multi-threaded shared variables.
InterlockedProvides methods that require atomic operations:

  • public static int Add (ref int location1, int value); add the two parameters and assign the result sum to the first parameter.
  • public static int Increment (ref int location); self-increase.
  • public static int CompareExchange (ref int location1, int value, int comparand);

location1 and comparison are compared, replaced by value.

value If the first parameter and the third parameter are equal, then the value is assigned to the first parameter.

compare and the first parameter.

ReaderWriterLock

If you want to make sure that a resource or data is up to date before it is accessed. Then it can be usedReaderWriterLock.This lock ensures that when a resource is assigned or updated, only it can access these resources itself, and no other thread can access them. That is, exclusive lock. However, when reading these data with a modified lock, an exclusive lock cannot be implemented.

lockOnly one thread is allowed to execute at the same time. andReaderWriterLockAllows multiple threads to perform read operations at the same time, or only one thread with exclusive locks to perform write operations.

 class Program
 {
 // Create an object public static ReaderWriterLock readerwritelock = new ReaderWriterLock();
 static void Main(string[] args)
 {
  //Create a thread to read data  Thread t1 = new Thread(Write);
  // (1);
  Thread t2 = new Thread(Write);
  //(2);
  // Create 10 threads to read data  for (int i = 3; i &lt; 6; i++)
  {
  Thread t = new Thread(Read);
  // (i);
  }

  ();

 }

 // Write method public static void Write(object i)
 {
  // Get the write lock, timeout of 20 milliseconds.  ("Thread:" + i + "Prepare to write...");
  ();
  ("Thread:" + i + "Write Operation" + );
  // Release the write lock  ("Thread:" + i + "Write end...");
  (1000);
  ();

 }

 // Reading method public static void Read(object i)
 {
  ("Thread:" + i + "Prepare to read...");

  // Get the read lock, 20ms timeout  ();
  ("Thread:" + i + "Read Operation" + );
  // Release the read lock  ("Thread:" + i + "Reading ends...");
  (1000);

  ();

 }
 }
//Block writer and reader methods respectively.  It can be seen more clearly that the writer is blocked.  The reader is not blocked.
//Block reader method//Thread: 1 ready to write...//Thread: 1 Write operation 2017/7/5 17:50:01//Thread: 1 write end...//Thread: 2 ready to write...//Thread: 2 Write operation 2017/7/5 17:50:02//Thread: 2 write end...
//Shield writer method//Thread: 3 ready to read...//Thread: 5 ready to read...//Thread: 4 ready to read...//Thread: 5 Read operation 2017/7/5 17:50:54//Thread: 5 read end...//Thread: 3 Read operation 2017/7/5 17:50:54//Thread: 3 read ends...//Thread: 4 Read operation 2017/7/5 17:50:54//Thread:4End of reading...

refer to:

  • MSDN
  • 《CLR via C#》

The above is the detailed content of in-depth analysis of C# thread synchronization. For more information about C# thread synchronization, please follow my other related articles!