无锁算法在C#中并非完全不加锁,而是不使用lock等阻塞原语,依赖Interlocked和CAS实现线程安全;线程不挂起但可能自旋耗CPU,适用于高频简单操作,复杂逻辑或协同更新应优先用lock。
“无锁”不是字面意义的完全不用锁,而是指不依赖 lock、Mutex、Monitor 这类阻塞式同步原语;它靠 Interlocked 系列方法(如 Interlocked.CompareExchange)和 CPU 提供的原子指令(CAS)实现线程安全。关键在于:线程不会因竞争而挂起,但可能自旋重试——这会吃 CPU,尤其在高争用时。
volatile 当成线程安全手段——它只保证可见性,不保证原子性或顺序性Interlocked 仅支持基础类型(int、long、ref 等),无法直接用于复杂对象状态更新ConcurrentQueue、ConcurrentStack 和 ConcurrentBag 在 .NET Core 2.1+ 及 .NET 5+ 中大量使用无锁策略(尤其是前两者底层基于 Interlocked + 分段数组),但不是“纯无锁”:它们在扩容、边界处理等少数路径仍会退化到细粒度锁(如 SpinLock 或内部 lock)。
ConcurrentDictionary 内部用分段锁(locks[] 数组),属于有锁优化而非无锁当你需要保护一段**非原子的多步逻辑**,或者涉及**多个共享变量的协同更新**,硬套无锁极易出错。例如:检查一个条件后再修改两个字段,CAS 无法一步完成这种“检查-执行”事务。
while (true) + CompareExchange 循环,并在里面做条件判断和分支赋值——这已偏离无锁初衷,且极易漏掉 ABA 问题lock 虽然阻塞,但在争用不激烈(如每秒几十次操作)、临界区极短(lock 对象粒度:避免锁住 this 或 typeof(MyClass),优先用私有 readonly object _sync = new();
private readonly object _sync = new(); private int _value; private string _status;// ✅ 推荐:用 lock 保护多字段协同更新 public void UpdateValueAndStatus(int newValue, string newStatus) { lock (_sync) { _value = newValue; _status = newStatus; // 还可能触发事件、更新缓存……这些无法用单一 CAS 表达 } }
// ❌ 避免:试图用 Interlocked 拆解多步逻辑(错误且不可靠) public void BadAttemptToUpdate() { while (true) { var oldVal = _value; var newVal = oldVal + 1; if (Interlocked.CompareExchange(ref _value, newVal, oldVal) == oldVal)
{ // 此时 _status 可能已被其他线程改写,状态不一致 _status = "updated"; // 这行不是原子的! break; } } }
无锁的真正门槛不在代码长度,而在对内存模型、CPU 缓存一致性协议(如 MESI)和 ABA 本质的理解。多数业务场景下,先用 Concurrent* 集合或 lock,压测发现瓶颈后再针对性替换为无锁实现——过早优化无锁,八成是在给自己埋坑。