线程同步和互斥锁概述
在多线程编程中,线程同步和互斥锁用于协调多个线程对共享资源的访问,防止由于并发导致的数据不一致或资源竞争问题。
- 线程同步: 确保多个线程按特定顺序或条件执行。
- 互斥锁: 通过强制线程互斥访问共享资源,避免资源竞争。
C# 提供了多种机制实现线程同步和互斥锁。
线程同步和互斥锁的常见实现方式
1. 使用lock关键字
lock 是 C# 提供的一种简单实现互斥锁的方式,它基于 Monitor 类,可以防止多个线程同时访问共享资源。
代码示例:
using System;
using System.Threading;
class Program
{
private static readonly object lockObject = new object();
private static int counter = 0;
static void IncrementCounter()
{
for (int i = 0; i < 5; i++)
{
lock (lockObject)
{
counter++;
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} incremented counter to {counter}");
}
Thread.Sleep(100);
}
}
static void Main()
{
Thread t1 = new Thread(IncrementCounter);
Thread t2 = new Thread(IncrementCounter);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine($"Final Counter Value: {counter}");
}
}
优点:
- 简单易用。
- 自动释放锁,避免死锁。
2. 使用Monitor类
Monitor 类提供比 lock 更细粒度的控制,比如 TryEnter 方法可以指定等待时间。
代码示例:
using System;
using System.Threading;
class Program
{
private static readonly object lockObject = new object();
static void AccessResource()
{
if (Monitor.TryEnter(lockObject, TimeSpan.FromSeconds(1)))
{
try
{
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} is accessing the resource.");
Thread.Sleep(500);
}
finally
{
Monitor.Exit(lockObject);
}
}
else
{
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} could not acquire the lock.");
}
}
static void Main()
{
Thread t1 = new Thread(AccessResource);
Thread t2 = new Thread(AccessResource);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
}
}
3. 使用Mutex类
Mutex 是一种系统级别的互斥锁,支持跨进程同步。
代码示例:
using System;
using System.Threading;
class Program
{
private static Mutex mutex = new Mutex();
static void AccessResource()
{
mutex.WaitOne(); // 获取互斥锁
try
{
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} is accessing the resource.");
Thread.Sleep(500);
}
finally
{
mutex.ReleaseMutex(); // 释放互斥锁
}
}
static void Main()
{
Thread t1 = new Thread(AccessResource);
Thread t2 = new Thread(AccessResource);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
}
}
特点:
- 可跨进程使用。
- 性能较 lock 和 Monitor 略低。
4. 使用Semaphore和SemaphoreSlim
- Semaphore: 限制多个线程访问资源,允许指定线程数。
- SemaphoreSlim: 是 Semaphore 的轻量级版本,仅限于单进程。
代码示例:
using System;
using System.Threading;
class Program
{
private static SemaphoreSlim semaphore = new SemaphoreSlim(2); // 允许最多 2 个线程同时访问
static void AccessResource()
{
semaphore.Wait(); // 获取信号量
try
{
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} is accessing the resource.");
Thread.Sleep(1000);
}
finally
{
semaphore.Release(); // 释放信号量
}
}
static void Main()
{
for (int i = 0; i < 5; i++)
{
new Thread(AccessResource).Start();
}
Console.ReadLine();
}
}
特点:
- 可用于控制并发访问数量。
- SemaphoreSlim 更适合单进程场景。
5. 使用ReaderWriterLockSlim
ReaderWriterLockSlim 支持读写锁机制,允许多个线程同时读取,但只有一个线程可以写入。
代码示例:
using System;
using System.Threading;
class Program
{
private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
private static int sharedResource = 0;
static void ReadResource()
{
rwLock.EnterReadLock();
try
{
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} read the value: {sharedResource}");
}
finally
{
rwLock.ExitReadLock();
}
}
static void WriteResource()
{
rwLock.EnterWriteLock();
try
{
sharedResource++;
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} wrote the value: {sharedResource}");
}
finally
{
rwLock.ExitWriteLock();
}
}
static void Main()
{
Thread t1 = new Thread(ReadResource);
Thread t2 = new Thread(WriteResource);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
}
}
特点:
- 提高读多写少场景的效率。
- 更适用于数据读写冲突多的场景。
线程同步和互斥锁的比较
机制 | 特性 | 适用场景 |
lock | 简单易用,仅限单进程,基于 Monitor 类 | 简单的互斥访问 |
Monitor | 提供更细粒度的控制,支持超时等功能 | 高级互斥控制 |
Mutex | 跨进程互斥,但性能稍低 | 跨进程的资源同步 |
Semaphore | 控制线程并发数量,可跨进程 | 限制线程数量 |
SemaphoreSlim | 轻量级版本,仅限单进程 | 限制线程数量(单进程) |
ReaderWriterLockSlim | 支持读写分离锁,读多写少时性能优异 | 数据读写冲突多的场景 |
总结
- 在单进程场景中,lock 是首选,简单高效。
- 对于跨进程同步,可以使用 Mutex 或 Semaphore。
- 在需要限制并发数量时,使用 Semaphore 或 SemaphoreSlim。
- 数据读多写少时,ReaderWriterLockSlim 是更好的选择。
根据场景合理选择同步机制,可以有效避免多线程编程中的竞态条件、数据不一致和死锁问题。