1. 线程同步技术概述
多线程编程是现代软件开发中提升性能的重要手段,但同时也带来了数据竞争和不一致性的风险。线程同步技术就是用来协调多个线程对共享资源的访问,确保程序行为的正确性和可靠性。
在C#中,线程同步技术主要分为以下几类:
- 锁机制:包括Monitor、Mutex、SemaphoreSlim等,通过互斥或信号量控制资源访问
- 无锁机制:基于原子操作(如Interlocked)实现线程安全
- 线程安全集合:如ConcurrentQueue、ConcurrentDictionary等内置同步的集合类
- 异步同步机制:支持异步编程的同步工具,如Channel、SemaphoreSlim.WaitAsync
- 其他辅助工具:如ManualResetEventSlim、AutoResetEvent、Barrier等
提示:选择同步技术时需要考虑性能开销、并发模型、异步需求和跨进程要求等因素。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 锁机制详解
2.1 Monitor(互斥锁)
Monitor是C#中最基础的同步机制,lock关键字是其语法糖。它确保同一时间只有一个线程可以访问共享资源。
实现原理:
- 每个.NET对象都包含一个同步块(sync block),用于存储锁状态
- Monitor.Enter标记对象为被占用状态,阻塞其他线程
- 内部使用操作系统同步原语(如Windows的Critical Section)
典型应用场景:
- 保护共享变量或集合的访问
- 实现生产者-消费者模式
- 需要线程协作的场景
代码示例:
csharp复制private readonly object _lock = new object();
private List<int> _sharedList = new List<int>();
void AddItem(int item)
{
lock(_lock)
{
_sharedList.Add(item);
}
}
性能特点:
- 用户态下性能较好
- 高争用情况下会退化为内核态等待
- 不支持跨进程同步
2.2 Mutex(互斥体)
Mutex是比Monitor更重量级的同步机制,支持跨进程同步。
关键特性:
- 可以命名,支持跨进程使用
- 支持线程所有权概念
- 可以设置等待超时
使用场景:
- 需要跨进程同步的场景
- 需要更精细控制的同步需求
代码示例:
csharp复制using(var mutex = new Mutex(false, "Global\\MyAppMutex"))
{
if(!mutex.WaitOne(TimeSpan.FromSeconds(5)))
{
// 超时处理
return;
}
try
{
// 访问共享资源
}
finally
{
mutex.ReleaseMutex();
}
}
2.3 SemaphoreSlim(轻量级信号量)
SemaphoreSlim是限制并发访问数量的轻量级同步机制。
核心特点:
- 维护一个计数器控制并发量
- 支持异步等待(WaitAsync)
- 比Semaphore更轻量
典型应用:
- 限制数据库连接池大小
- 控制硬件设备并发访问
- 异步编程中的资源管理
代码示例:
csharp复制private SemaphoreSlim _semaphore = new SemaphoreSlim(5); // 允许5个并发
async Task AccessResourceAsync()
{
await _semaphore.WaitAsync();
try
{
// 访问受限资源
}
finally
{
_semaphore.Release();
}
}
3. 无锁编程技术
3.1 Interlocked类
Interlocked提供原子操作,无需显式锁即可实现简单线程安全。
常用方法:
- Increment/Decrement:原子增减
- Exchange:原子交换
- CompareExchange:比较并交换
适用场景:
- 简单的计数器操作
- 标志位更新
- 指针/引用交换
代码示例:
csharp复制private int _counter = 0;
void IncrementCounter()
{
Interlocked.Increment(ref _counter);
}
3.2 内存屏障
内存屏障用于控制指令重排序,确保内存访问顺序。
主要类型:
- Thread.MemoryBarrier()
- Volatile读写
- Interlocked方法隐含屏障
使用场景:
- 实现无锁数据结构
- 需要精确控制内存可见性的场景
4. 线程安全集合
.NET提供了一系列线程安全的并发集合类。
4.1 ConcurrentQueue
线程安全的先进先出队列。
特点:
- 无锁实现
- 适合生产者-消费者模式
- 支持批量操作
代码示例:
csharp复制var queue = new ConcurrentQueue<int>();
// 生产者
queue.Enqueue(1);
// 消费者
if(queue.TryDequeue(out var item))
{
// 处理item
}
4.2 ConcurrentDictionary
线程安全的字典实现。
关键特性:
- 细粒度锁
- 原子操作方法
- 高性能并发访问
代码示例:
csharp复制var dict = new ConcurrentDictionary<string, int>();
// 添加或更新
dict.AddOrUpdate("key", 1, (k, v) => v + 1);
// 获取值
if(dict.TryGetValue("key", out var value))
{
// 使用value
}
5. 高级同步技术
5.1 ReaderWriterLockSlim
读写锁,支持多读单写。
适用场景:
- 读多写少的场景
- 需要区分读写访问的情况
代码示例:
csharp复制private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
int ReadData()
{
_rwLock.EnterReadLock();
try
{
// 读取操作
return _data;
}
finally
{
_rwLock.ExitReadLock();
}
}
void WriteData(int value)
{
_rwLock.EnterWriteLock();
try
{
// 写入操作
_data = value;
}
finally
{
_rwLock.ExitWriteLock();
}
}
5.2 Barrier
屏障同步,用于协调多个线程在某个点汇合。
典型应用:
- 分阶段并行计算
- 多线程测试
- 复杂并行算法
代码示例:
csharp复制var barrier = new Barrier(3, b =>
{
Console.WriteLine($"Phase {b.CurrentPhaseNumber} completed");
});
void Worker()
{
// 阶段1工作
barrier.SignalAndWait();
// 阶段2工作
barrier.SignalAndWait();
}
6. 异步同步机制
6.1 Channel
.NET中的异步生产者-消费者队列。
核心概念:
- 创建Channel
- Writer写入数据
- Reader读取数据
代码示例:
csharp复制var channel = Channel.CreateUnbounded<int>();
// 生产者
async Task ProducerAsync()
{
for(int i = 0; i < 10; i++)
{
await channel.Writer.WriteAsync(i);
}
channel.Writer.Complete();
}
// 消费者
async Task ConsumerAsync()
{
await foreach(var item in channel.Reader.ReadAllAsync())
{
Console.WriteLine(item);
}
}
6.2 AsyncLock
基于SemaphoreSlim实现的异步锁。
实现示例:
csharp复制public class AsyncLock
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
public async Task<IDisposable> LockAsync()
{
await _semaphore.WaitAsync();
return new Releaser(_semaphore);
}
private class Releaser : IDisposable
{
private readonly SemaphoreSlim _semaphore;
public Releaser(SemaphoreSlim semaphore)
{
_semaphore = semaphore;
}
public void Dispose()
{
_semaphore.Release();
}
}
}
7. 性能考量与最佳实践
7.1 同步技术性能对比
| 技术 | 适用场景 | 性能特点 | 线程阻塞 | 跨进程 |
|---|---|---|---|---|
| Monitor | 一般互斥 | 中等 | 是 | 否 |
| Mutex | 跨进程同步 | 较低 | 是 | 是 |
| SemaphoreSlim | 限制并发 | 较高 | 可选 | 否 |
| Interlocked | 简单原子操作 | 最高 | 否 | 否 |
| Concurrent集合 | 并发数据结构 | 高 | 无 | 否 |
7.2 常见问题与解决方案
问题1:死锁
- 原因:多个锁获取顺序不一致
- 解决方案:
- 统一锁获取顺序
- 使用超时机制
- 避免嵌套锁
问题2:锁竞争
- 原因:过多线程争用同一锁
- 解决方案:
- 减小锁粒度
- 使用无锁技术
- 考虑分区处理
问题3:内存可见性
- 原因:CPU缓存导致数据不一致
- 解决方案:
- 使用Volatile关键字
- 适当插入内存屏障
- 使用Interlocked操作
8. 实际应用案例
8.1 上位机硬件通信
在多线程硬件通信场景中,通常需要:
- 使用互斥锁保护硬件访问
- 信号量限制并发请求数
- 异步机制处理I/O等待
csharp复制class HardwareController
{
private readonly object _hwLock = new object();
private readonly SemaphoreSlim _concurrencyLimiter = new SemaphoreSlim(3);
public async Task<byte[]> SendCommandAsync(byte[] command)
{
await _concurrencyLimiter.WaitAsync();
try
{
lock(_hwLock)
{
// 发送硬件命令
// 接收响应
return response;
}
}
finally
{
_concurrencyLimiter.Release();
}
}
}
8.2 高并发数据处理
对于高并发数据处理,推荐:
- 使用ConcurrentQueue作为缓冲区
- 多个消费者线程处理数据
- Channel实现异步管道
csharp复制async Task ProcessDataAsync(IEnumerable<Data> dataSource)
{
var channel = Channel.CreateUnbounded<Data>();
// 生产者
var producer = Task.Run(() =>
{
foreach(var data in dataSource)
{
channel.Writer.TryWrite(data);
}
channel.Writer.Complete();
});
// 消费者
var consumers = Enumerable.Range(0, 4).Select(_ => Task.Run(async () =>
{
await foreach(var data in channel.Reader.ReadAllAsync())
{
Process(data);
}
}));
await Task.WhenAll(consumers);
await producer;
}
9. 技术选型指南
选择线程同步技术时,建议考虑以下因素:
-
并发模型:
- 是否需要互斥访问?
- 是读多写少还是读写均衡?
- 需要跨进程同步吗?
-
性能需求:
- 预期并发量有多大?
- 同步开销是否敏感?
- 能否接受线程阻塞?
-
编程模型:
- 使用同步还是异步编程?
- 是否需要支持取消操作?
- 错误处理需求如何?
-
维护成本:
- 代码可读性和可维护性
- 调试难度
- 未来扩展性
根据实际项目经验,以下是一些推荐选择:
- 一般互斥:优先考虑Monitor或lock
- 高性能场景:考虑Interlocked或无锁算法
- 异步编程:使用SemaphoreSlim.WaitAsync或Channel
- 跨进程需求:使用Mutex或Semaphore
- 读多写少:ReaderWriterLockSlim
