在 C# 中,除了
lock
语句之外,还有多种常用的锁机制,每种机制都有其特点和适用场景,以下为你详细介绍:
1. Monitor
类
- 原理:
Monitor
类是 C# 中用于实现同步的基础类,lock
语句实际上是Monitor
类的语法糖。Monitor
类提供了Enter
和Exit
方法来获取和释放锁,还提供了TryEnter
方法用于尝试获取锁而不阻塞线程。 - 适用场景:当需要更灵活地控制锁的获取和释放,例如在获取锁之前进行一些额外的检查,或者在不同的代码块中释放锁时,可以使用
Monitor
类。 - 示例代码:
using System;
using System.Threading;
class Program
{
private static readonly object _lockObj = new object();
private static int _sharedResource = 0;
static void Main()
{
Thread t1 = new Thread(Increment);
Thread t2 = new Thread(Increment);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine($"Final value of shared resource: {_sharedResource}");
}
static void Increment()
{
bool lockTaken = false;
try
{
Monitor.Enter(_lockObj, ref lockTaken);
for (int i = 0; i < 100000; i++)
{
_sharedResource++;
}
}
finally
{
if (lockTaken)
{
Monitor.Exit(_lockObj);
}
}
}
}
2. Mutex
类
- 原理:
Mutex
(互斥体)是一种跨进程的同步原语,它可以确保在同一时间只有一个线程或进程可以访问共享资源。与Monitor
不同,Mutex
可以用于不同进程之间的同步。 - 适用场景:适用于需要在多个进程之间同步访问共享资源的场景,例如多个进程同时访问一个文件或数据库资源。
- 示例代码:
using System;
using System.Threading;
class Program
{
private static Mutex _mutex = new Mutex();
private static int _sharedResource = 0;
static void Main()
{
Thread t1 = new Thread(Increment);
Thread t2 = new Thread(Increment);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine($"Final value of shared resource: {_sharedResource}");
}
static void Increment()
{
for (int i = 0; i < 100000; i++)
{
_mutex.WaitOne();
try
{
_sharedResource++;
}
finally
{
_mutex.ReleaseMutex();
}
}
}
}
3. SemaphoreSlim
类
- 原理:
SemaphoreSlim
是一个轻量级的信号量,它可以控制同时访问某个资源的线程数量。信号量维护一个计数器,当线程请求访问资源时,计数器减 1;当线程释放资源时,计数器加 1。当计数器为 0 时,后续请求访问的线程会被阻塞。 - 适用场景:适用于需要限制并发访问数量的场景,例如数据库连接池、线程池等,确保同时访问资源的线程数量不超过一定限制。
- 示例代码:
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
private static SemaphoreSlim _semaphore = new SemaphoreSlim(2); // 最多允许2个线程同时访问
private static int _sharedResource = 0;
static async Task Main()
{
Task[] tasks = new Task[5];
for (int i = 0; i < 5; i++)
{
tasks[i] = Task.Run(Increment);
}
await Task.WhenAll(tasks);
Console.WriteLine($"Final value of shared resource: {_sharedResource}");
}
static async Task Increment()
{
await _semaphore.WaitAsync();
try
{
for (int i = 0; i < 100000; i++)
{
_sharedResource++;
}
}
finally
{
_semaphore.Release();
}
}
}
4. ReaderWriterLockSlim
类
- 原理:
ReaderWriterLockSlim
允许多个线程同时进行读操作,但在进行写操作时会独占资源,不允许其他线程进行读或写操作。这种锁机制可以提高并发性能,因为读操作通常不会相互影响。 - 适用场景:适用于读操作频繁,写操作较少的场景,例如缓存系统,多个线程可以同时读取缓存数据,但在更新缓存时需要独占资源。
- 示例代码:
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
private static ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
private static int _sharedResource = 0;
static async Task Main()
{
Task[] tasks = new Task[5];
for (int i = 0; i < 3; i++)
{
tasks[i] = Task.Run(Read);
}
for (int i = 3; i < 5; i++)
{
tasks[i] = Task.Run(Write);
}
await Task.WhenAll(tasks);
Console.WriteLine($"Final value of shared resource: {_sharedResource}");
}
static void Read()
{
_rwLock.EnterReadLock();
try
{
Console.WriteLine($"Read value: {_sharedResource}");
}
finally
{
_rwLock.ExitReadLock();
}
}
static void Write()
{
_rwLock.EnterWriteLock();
try
{
_sharedResource++;
Console.WriteLine($"Write new value: {_sharedResource}");
}
finally
{
_rwLock.ExitWriteLock();
}
}
}
5. SpinLock
结构
- 原理:
SpinLock
是一种自旋锁,当线程尝试获取锁时,如果锁已经被其他线程持有,该线程会在一个循环中不断检查锁的状态,而不是进入阻塞状态。这种方式避免了线程上下文切换的开销,但会占用 CPU 资源。 - 适用场景:适用于锁的持有时间非常短的场景,因为自旋等待可以避免线程上下文切换带来的性能开销。
- 示例代码:
using System;
using System.Threading;
class Program
{
private static SpinLock _spinLock = new SpinLock();
private static int _sharedResource = 0;
static void Main()
{
Thread t1 = new Thread(Increment);
Thread t2 = new Thread(Increment);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine($"Final value of shared resource: {_sharedResource}");
}
static void Increment()
{
bool lockTaken = false;
for (int i = 0; i < 100000; i++)
{
try
{
_spinLock.Enter(ref lockTaken);
_sharedResource++;
}
finally
{
if (lockTaken)
{
_spinLock.Exit();
lockTaken = false;
}
}
}
}
}
C# 提供了多种锁机制,每种锁都有其适用场景。
lock
语句适用于简单的同步场景;Monitor
类提供了更灵活的锁控制;Mutex
类适用于跨进程的同步;SemaphoreSlim
类适用于限制并发访问数量的场景。在实际开发中,需要根据具体的需求选择合适的锁机制,以确保程序的正确性和性能。