在 C#(.NET)中,线程同步机制根据其实现方式和资源调度层级,可分为三类:用户模式锁(User-Mode Locks)、内核模式锁(Kernel-Mode Locks) 和 混合模式锁(Hybrid Locks)。它们在性能、功能、适用场景上有显著差异。
下面从原理、特点、使用示例、适用场景四个方面,对这三类锁进行系统性介绍。
一、用户模式锁(User-Mode Locks)
✅ 原理
完全在用户空间实现,不涉及操作系统内核对象。线程在等待时不会让出 CPU,通常采用“忙等待”(自旋)方式。
⚙️ 特点
- 速度快(无上下文切换开销)
- 不能跨进程
- 无法让线程休眠(只能自旋)
- 适合极短临界区(微秒级)
- 高 CPU 占用风险
🔧 主要类型及示例
1. volatile(内存屏障 + 可见性保证)
public class VolatileExample
{
public volatile bool _shouldStop = false;
public void Worker()
{
while (!_shouldStop) { /* 工作 */ }
}
public void Stop() => _shouldStop = true;
}
说明:确保
_shouldStop的读写直接作用于主内存,防止编译器/CPU 重排序。不提供原子性!
2. Interlocked(原子操作)
private long _counter = 0;
public void Increment() => Interlocked.Increment(ref _counter);
public long GetCount() => Interlocked.Read(ref _counter);
适用于计数器、状态切换等无锁编程场景。
3. SpinLock(自旋互斥锁)
private SpinLock _spinLock = new SpinLock();
public void CriticalSection()
{
bool lockTaken = false;
try
{
_spinLock.Enter(ref lockTaken);
// 临界区(必须非常短!)
}
finally
{
if (lockTaken) _spinLock.Exit();
}
}
注意:不可重入;同一线程二次进入会死锁。
💡 .NET 中没有名为
SimpleSpinLock的标准类型,通常是教学或自定义的基于Interlocked.CompareExchange的简易自旋锁。
二、内核模式锁(Kernel-Mode Locks)
✅ 原理
依赖 Windows 内核对象(如 Event、Mutex、Semaphore),线程等待时会进入内核态并被挂起,由操作系统调度。
⚙️ 特点
- 支持跨进程同步
- 线程可休眠,不占 CPU
- 上下文切换开销大(约 1000~3000 纳秒)
- 功能强大但性能较低
🔧 主要类型及示例
1. Mutex(互斥体,支持跨进程)
// 全局命名 Mutex,确保单实例应用
using var mutex = new Mutex(false, "MyAppSingleton");
if (mutex.WaitOne(TimeSpan.FromSeconds(5)))
{
try
{
// 应用主逻辑
}
finally
{
mutex.ReleaseMutex();
}
}
else
{
Console.WriteLine("Another instance is running.");
}
2. AutoResetEvent(自动重置事件)
var are = new AutoResetEvent(false);
Task.Run(() =>
{
Console.WriteLine("Worker waiting...");
are.WaitOne(); // 阻塞
Console.WriteLine("Work resumed!");
});
Thread.Sleep(1000);
are.Set(); // 仅唤醒一个等待线程,然后自动重置
3. ManualResetEvent(手动重置事件)
var mre = new ManualResetEvent(false);
Task.Run(() => { mre.WaitOne(); Console.WriteLine("Thread 1 go!"); });
Task.Run(() => { mre.WaitOne(); Console.WriteLine("Thread 2 go!"); });
Thread.Sleep(1000);
mre.Set(); // 所有等待线程同时继续
// mre.Reset(); // 手动重置为非信号状态
4. Semaphore(信号量,限制并发数)
var sem = new Semaphore(initialCount: 2, maximumCount: 2); // 最多2个并发
for (int i = 0; i < 5; i++)
{
Task.Run(() =>
{
sem.WaitOne();
try
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} entered");
Thread.Sleep(2000);
}
finally
{
sem.Release();
}
});
}
❗ 注意:
ReaderWriterLock是旧版读写锁,已被ReaderWriterLockSlim取代,不推荐使用。
三、混合模式锁(Hybrid Locks)
✅ 原理
结合用户模式与内核模式:先尝试用户态自旋(忙等待),若短时间内未获得锁,则退化为内核等待。兼顾低延迟与低 CPU 开销。
⚙️ 特点
- 高性能(短竞争时无内核切换)
- 自动适应负载
- .NET 中最常用的同步原语
🔧 主要类型及示例
1. lock / Monitor(最常用互斥锁)
private readonly object _lock = new object();
public void SafeMethod()
{
lock (_lock) // 等价于 Monitor.Enter/Exit
{
// 临界区
}
}
底层是混合锁:先轻量级用户模式同步,失败后使用内核事件。
2. SemaphoreSlim(轻量信号量,支持异步)
private SemaphoreSlim _sem = new SemaphoreSlim(2);
public async Task AccessResourceAsync()
{
await _sem.WaitAsync(); // 异步等待,不阻塞线程
try
{
await Task.Delay(1000); // 模拟异步工作
}
finally
{
_sem.Release();
}
}
不支持跨进程,但性能远高于
Semaphore。
3. ManualResetEventSlim(轻量事件)
var slim = new ManualResetEventSlim(false);
Task.Run(() => { slim.Wait(); Console.WriteLine("Go!"); });
Thread.Sleep(1000);
slim.Set(); // 触发所有等待者
内部先自旋,超时后才使用内核事件。
4. ReaderWriterLockSlim(高效读写锁)
private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
public string ReadData()
{
_rwLock.EnterReadLock();
try
{
return _data;
}
finally
{
_rwLock.ExitReadLock();
}
}
public void WriteData(string value)
{
_rwLock.EnterWriteLock();
try
{
_data = value;
}
finally
{
_rwLock.ExitWriteLock();
}
}
适用于“读多写少”场景(如缓存)。
5. Lazy<T>(线程安全延迟初始化)
private static readonly Lazy<ExpensiveObject> _instance =
new Lazy<ExpensiveObject>(() => new ExpensiveObject(),
LazyThreadSafetyMode.ExecutionAndPublication);
public static ExpensiveObject Instance => _instance.Value;
内部使用混合锁实现双检锁,比手写更安全高效。
6. Barrier(阶段同步)
var barrier = new Barrier(3, b => Console.WriteLine($"Phase {b.CurrentPhaseNumber} done"));
Parallel.For(0, 3, i =>
{
Console.WriteLine($"Task {i}: Phase 1");
barrier.SignalAndWait(); // 所有任务到达后继续
Console.WriteLine($"Task {i}: Phase 2");
barrier.SignalAndWait();
});
7. CountdownEvent(倒计时事件)
var cde = new CountdownEvent(3);
for (int i = 0; i < 3; i++)
{
Task.Run(() =>
{
Thread.Sleep(1000);
cde.Signal(); // 完成一个任务
});
}
cde.Wait(); // 等待所有 Signal 调用完成
Console.WriteLine("All tasks done!");
四、三类锁对比总结
| 特性 | 用户模式锁 | 内核模式锁 | 混合模式锁 |
|---|---|---|---|
| 是否进入内核 | 否 | 是 | 条件性(先否后是) |
| CPU 开销 | 高(忙等待) | 低(线程休眠) | 自适应(低) |
| 延迟 | 极低 | 高 | 低(短竞争时) |
| 跨进程支持 | ❌ | ✅ | ❌ |
| 典型代表 | SpinLock, Interlocked | Mutex, Semaphore | lock, SemaphoreSlim |
| 适用场景 | 极短临界区、无锁结构 | 跨进程、长时间等待 | 通用高性能同步 |
五、最佳实践建议
- 优先使用混合锁:如
lock、SemaphoreSlim、Lazy<T>,它们在大多数场景下性能与易用性最佳。 - 避免手写双检锁:用
Lazy<T>替代。 - 异步场景用
*Slim类型:如SemaphoreSlim.WaitAsync()。 - 仅在极端性能场景用用户锁:如高频无锁队列(需深厚并发知识)。
- 内核锁用于跨进程或长时间等待:如单例应用用
Mutex。
通过合理选择锁类型,可以在保证线程安全的同时,最大化程序性能与响应能力。
2525

被折叠的 条评论
为什么被折叠?



