Multithreading in. NET (4. Use locks for synchronization) and. net Multithreading
Implement Synchronization Through Locks
The exclusive lock is mainly used to ensure that only one thread can access a piece of code within a period of time. The two main types of exclusive locks are lock and Mutex. Compared with Mutex, Lock is easier to construct and run. However, Mutex can be used by different processes on the same machine.
Monitor. Enter and Monitor. Exit
The lock keyword in C # is actually a shorthand for Monitor. Enter and Monitor. Exist. In c # Of. NET 1.0, 2.0, and 3.0, lock is compiled into the following code:
Monitor.Enter(_locker); try { if (_val2 != 0) Console.WriteLine(_val1 / _val2); _val2 = 0; } finally { Monitor.Exit(_locker); }
If you do not call Monitor. Enter and directly call Monitor. Exit, an exception is thrown.
LockTaken version:
Imagine the above Code, if you Monitor again. after entering, before try, the thread encountered an exception (such as being terminated). In this case, the Exit method in finally will never be executed, and the lock will not be released. To avoid this, the designers of CLR 4.0 reload the Monitor. Enter method:
public static void Enter (object obj, ref bool lockTaken);
If the lock is not obtained by the current thread due to some exceptions, the lockTake value will be false. Therefore, in CLR 4.0, the lock will be interpreted as the following code:
bool lockTaken = false; try { Monitor.Enter(_locker, ref lockTaken); // Do your stuff... } finally { if (lockTaken) Monitor.Exit(_locker); }
TryEnter
Monitor also provides a TryEnter method that allows you to set a timeout time to prevent the current thread from waiting until the lock is obtained for a long time.
Select the correct synchronization object
You need to select a lock (obj) for objects visible to all threads to ensure that the program can be executed according to your intent. If you do not know some features of C #, the lock may not be executed as expected.
When to use lock
For a basic rule, you need to lock any write operation or modifiable fields. Even if it is a value assignment operation or a accumulate operation, you cannot assume that it is thread-safe.
For example, the following code is not thread-safe:
class ThreadUnsafe { static int _x; static void Increment() { _x++; } static void Assign() { _x = 123; } }
You need to write:
class ThreadSafe { static readonly object _locker = new object(); static int _x; static void Increment() { lock (_locker) _x++; } static void Assign() { lock (_locker) _x = 123; } }
If you have read some implementations in the BCL class library, you can find that the InterLocked class is used in some cases instead of the lock class. We will introduce it later.
About nested locks or reentrant
When you read some documents, some documents may say that lock or Monitor. Enter is reentrant (reentrant). How do we understand reentrant?
Imagine the following code:
lock (locker) lock (locker) lock (locker) { // Do something... }
Or:
Monitor.Enter(locker); Monitor.Enter(locker); Monitor.Enter(locker); // Do something... Monitor.Exit(locker); Monitor.Exit(locker); Monitor.Exit(locker);
In this case, locker is available only after the last exit is executed or after the corresponding number of exits is executed.
Mutex
Mutex is like lock in c #, but it can still be used in different processes. In other words, Mutex is a computer-Level Lock. Therefore, obtaining such a lock is much slower than the Monitor.
Sample Code:
using System;using System.Threading.Tasks;using System.Threading;namespace MultiThreadTest{ class OneAtATimePlease { static void Main() { // Naming a Mutex makes it available computer-wide. Use a name that's // unique to your company and application (e.g., include your URL). using (var mutex = new Mutex(false, "oreilly.com OneAtATimeDemo")) { // Wait a few seconds if contended, in case another instance // of the program is still in the process of shutting down. if (!mutex.WaitOne(TimeSpan.FromSeconds(3), false)) { Console.WriteLine("Another app instance is running. Bye!"); return; } RunProgram(); } } static void RunProgram() { Console.WriteLine("Running. Press Enter to exit"); Console.ReadLine(); } }}
Semaphore
Both Monitor and Mutex are exclusive locks, and Semaphore is another non-exclusive locks.
We use it to implement an example: a bar can accommodate up to three people. If it is full, you need to wait. When a guest leaves, the waiting person can come in at any time.
Sample Code:
using System;using System.Threading;class TheClub // No door lists!{ static Semaphore _sem = new Semaphore(3, 3); // Capacity of 3 static void Main() { for (int i = 1; i <= 5; i++) new Thread(Enter).Start(i); Console.ReadLine(); } static void Enter(object id) { Console.WriteLine(id + " wants to enter"); _sem.WaitOne(); Console.WriteLine(id + " is in!"); // Only three threads Thread.Sleep(1000 * (int)id); // can be here at Console.WriteLine(id + " is leaving"); // a time. _sem.Release(); }}
To use Semaphore, you need to call the caller to control the resource access, call WaitOne to obtain the resource, and Release the resource through Release. Developers have the responsibility to ensure that resources can be correctly released.
Semaphore is very useful when restricting synchronous access. It does not need to wait for all other threads when a thread accesses certain resources like Monitor or Mutex, but instead sets a buffer zone, the maximum number of threads allowed for simultaneous access.
Semaphore can also be synchronized across processes like Mutex.
This section mainly summarizes the use of locks for synchronization, the next section will summarize the use of semaphores for synchronization.