目录

并发工具

lockLazy<T>InterlockedSemaphoreSlim/ReaderWriterLockvolatile.

1.lock -最基本的同步方式

lock 是 .NET中最常用的同步工具,用来确保一个线程在执行特定代码时,其它线程无法同时执行这段代码.

private readonly object _lockObj = new object();

public void CriticalSection()
{
    lock(_lockObj)
    {
       // 只有一个线程能够进入这里
       // 其他线程在这里会被阻塞
       // 执行临界操作
    }
}

工作原理:

  • lock 是对 Monitor.EnterMonitor.Exit 的封装.
  • 每次进入 lock 时,会 获取对象的锁, 并且在操作结束后,释放锁.

注意事项:

  • 锁定的是对象实例(通常是 readonly 对象, 避免锁对象被修改)。
  • 如果 锁的粒度过大 (例如锁住整个方法), 可能导致性能瓶颈。
  • 避免死锁: 确保在所有地方都用相同的顺序获取锁。

2.Lazy - 延迟初始化

Lazy 用于实现 延迟初始化 ,即只有在第一次使用对象时才进行实例化。它可以保证线程安全,适用于对象在多线程环境下的按需创建。

Lazy<MyClass> lazyObject = new Lazy<MyClass>(() => new MyClass());

public MyClass GetObject()
{
   return lazyObject.Value; // 只有第一次访问时才会创建 MyClass 实例
}

工作原理:

  • 当访问 lazyObject.Value 时, Lazy 会判断是否已初始化对象。
  • 如果未初始化, 会 安全地初始化 (单线程中也能保证线程安全)。

线程安全:

Lazy 默认使用 **LazyThreadSafetyMode.ExecutionAndPublication,**意味着它支持线程安全的单次初始化。

3.Interlocked - 原子操作

Interlocked提供了一些 原子操作,用于多线程中避免竞争条件。它确保对某些基本数据类型(如 int )的 操作是 不可分割的。

常见用法:

  • Interlocked.Add(ref int location, int value)
  • Interlocked.CompareExchange(ref T location, T value, T comparand)
  • Interlocked.Exchange(ref T location, T value)
  • Interlocked.Decrement(ref int location)
  • interlocked.Increment(ref int location)

Interlocked 可以用于简单的数值类型 ( 如 int) , 使用于技术,累加, 比较和交换等操作。它能在多线程中避免不必要的锁。

private int counter = 0;

public void IncrementCounter()
{
    Interlocked.Increment(ref counter); // 线程安全地增加计数器
}

适用场景:

  • 用于数值类型操作, 避免显式锁。
  • 适用于多线程下的技术、标志位等操作。

4.SemaphoreSlim 和 ReaderWriterLockSlim

这两者用于更细粒度地线程同步,适用于需要 控制对资源的并发访问 的场景。

SemaphoreSlim - 信号量

SemaphoreSlim 用于 限制线程数目, 控制资源的并发访问。它是比 lock 更轻量的方式,适用于对资源数量有限的场景。

private SemaphoreSlim _semaphore = new SemaphoreSlim(3); // 允许最多 3 个线程同时访问

public async Task DoWork()
{
    await _semaphore.WaitAsync();
    try
    {
       // 只有 3 个线程可以同时访问这里
       // 执行任务
    }
    finally
    {
       _semaphore.Release(); // 释放信号量
    }

}

适用场景:

  • 限制并发线程数(例如限制数据库连接池大小)。
  • 适用于高并发但有限资源的场景。

ReaderWriterLockSlim - 读写锁

ReaderWriterLockSlim 是一种 ,适用于 读多写少 的场景。它允许多个线程同时读取,但 只有一个线程 能写入。

private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

public void ReadData()
{
    _lock.EnterReadLock();  // 获取读锁
    try
    {
       // 多线程可以同时读取数据
    }
    finally
    {
       _lock.ExitReadLock(); // 释放读锁
    }
}

public void WriteData()
{
    _lock.EnterWirteLock(); //  获取写锁
    try
    {
       // 只有一个线程可以写数据
    }
    finally
    {
       _lock.ExitWriteLock(); // 释放写锁
    }
}

适用场景:

  • 读多写少 的数据结构 (如缓存, 数据库等)
  • 多线程并发读取,写操作比较少的场景。

5.volatile - 保证内存可见性

volatitle 修饰符用于确保 变量的更新立即对所有线程可见。它主要保证的是 内存可见性,而非原子性。

private volatitle isFlagChanged;

pubic void UpdateFlag()
{
   isFlagChanged = true;
}

public bool CheckFlag()
{
    return isFlagChanged;
}

工作原理:

  • volatitle 修饰的字段的写入,所有线程都能立刻看到最新的值。
  • 避免 CPU 或缓存优化导致某些线程看到的值过时。

volatitle 并不保证原子性。例如,不能用 volatitle 来保证整数加法操作是线程安全的,需要配合 Interlocked或其它同步机制。

总结:

工具 线程安全性 适用场景

lock 提供互斥锁(互斥访问某些资源); 简单的临界区保护(对象、集合、文件等)

Lazy<T> 延迟初始化,线程安全; 惰性加载对象(仅初始化一次,线程安全)

Interlocked 原子操作(适合数值类型操作); 计数器、标志位、累加、比较交换等操作

SemaphoreSlim 限制并发线程数目(轻量级信号量); 控制并发访问的线程数(如连接池)ReaderWriterLockSlim 允许多个线程读、一个线程写; 读多写少的场景(如缓存)

volatile 保证内存可见性; 变量更新必须立即被所有线程看到

  • lock 是最基本的同步工具,但它的性能开销较大,适合简单的临界区控制。
  • Lazy<T> 适用于延迟初始化,且自带线程安全保障。
  • Interlocked 提供了非常高效的原子操作,用于简单的数值类型操作,避免锁开销。
  • SemaphoreSlimReaderWriterLockSlim 是用于更精细的并发控制,适用于需要限制并发量和读写分离的场景。
  • volatile 用于保证字段在多线程环境中的内存可见性,但不保证原子性,不能单独依赖它来做线程安全的操作。