C# 锁概述

在 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, InterlockedMutex, Semaphorelock, SemaphoreSlim
适用场景极短临界区、无锁结构跨进程、长时间等待通用高性能同步

五、最佳实践建议

  1. 优先使用混合锁:如 lockSemaphoreSlimLazy<T>,它们在大多数场景下性能与易用性最佳。
  2. 避免手写双检锁:用 Lazy<T> 替代。
  3. 异步场景用 *Slim 类型:如 SemaphoreSlim.WaitAsync()
  4. 仅在极端性能场景用用户锁:如高频无锁队列(需深厚并发知识)。
  5. 内核锁用于跨进程或长时间等待:如单例应用用 Mutex

通过合理选择锁类型,可以在保证线程安全的同时,最大化程序性能与响应能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值