You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何无锁更新缓存列表,避免多线程更新时的覆盖问题?

无锁实现不可变用户列表缓存的并发安全更新

核心思路

你的原始方案逻辑是合理的——用不可变列表避免读取阶段的线程安全问题,问题出在更新操作的原子性缺失。要在不使用lock的前提下解决,核心是通过原子比较替换操作,确保每次更新都基于最新的缓存状态完成,从根源上避免覆盖问题。

C# 具体实现方案

1. 选用ImmutableList<T>作为缓存容器

System.Collections.Immutable命名空间下的ImmutableList<T>是专为不可变集合设计的类,所有修改操作(添加、删除、更新)都会返回全新的列表实例,完全契合你返回不可变列表的需求。

2. 用Interlocked.CompareExchange实现原子更新

Interlocked.CompareExchange是C#提供的原子操作方法,它会完成两个核心动作:

  • 比较目标变量的当前值与预期值
  • 若匹配,则将目标变量替换为新值并返回旧值;若不匹配,直接返回当前值(说明期间有其他线程修改了变量)

利用这个特性,我们可以实现无锁的循环重试逻辑,确保最终的更新总是基于最新的缓存状态。

完整代码示例

using System.Collections.Immutable;
using System.Threading;

public class UserCache
{
    // 初始化空的不可变用户列表
    private ImmutableList<User> _cachedUsers = ImmutableList<User>.Empty;

    // 获取不可变用户列表(直接返回,无需额外处理)
    public ImmutableList<User> GetAllUsers()
    {
        return _cachedUsers;
    }

    // 添加用户的无锁更新逻辑
    public void AddUser(User newUser)
    {
        while (true)
        {
            // 获取当前缓存的快照
            var currentCache = _cachedUsers;
            // 基于快照创建新列表(执行添加操作)
            var newCache = currentCache.Add(newUser);
            // 原子比较替换:仅当当前缓存仍为快照值时,才替换为新列表
            var originalCache = Interlocked.CompareExchange(ref _cachedUsers, newCache, currentCache);
            
            // 替换成功则退出循环
            if (originalCache == currentCache)
                break;
            
            // 替换失败说明有其他线程修改了缓存,重新循环重试
        }
    }

    // 更新用户的无锁更新逻辑
    public void UpdateUser(User updatedUser)
    {
        while (true)
        {
            var currentCache = _cachedUsers;
            var userIndex = currentCache.FindIndex(u => u.Id == updatedUser.Id);
            if (userIndex == -1)
                break; // 用户不存在,无需更新
            
            var newCache = currentCache.SetItem(userIndex, updatedUser);
            var originalCache = Interlocked.CompareExchange(ref _cachedUsers, newCache, currentCache);
            
            if (originalCache == currentCache)
                break;
        }
    }

    // 删除用户的无锁更新逻辑
    public void RemoveUser(Guid userId)
    {
        while (true)
        {
            var currentCache = _cachedUsers;
            var userToRemove = currentCache.FirstOrDefault(u => u.Id == userId);
            if (userToRemove == null)
                break; // 用户不存在,无需删除
            
            var newCache = currentCache.Remove(userToRemove);
            var originalCache = Interlocked.CompareExchange(ref _cachedUsers, newCache, currentCache);
            
            if (originalCache == currentCache)
                break;
        }
    }
}

// 示例User类
public class User
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    // 其他业务属性...
}

方案优势

  • 无锁并发:避免了lock带来的线程阻塞,高并发场景下性能更优
  • 数据一致:原子操作确保每次更新都基于最新缓存状态,不会出现后写入覆盖先写入的问题
  • 天然不可变:ImmutableList<T>确保外部获取的列表无法被修改,彻底消除读取阶段的线程安全风险
  • 轻量高效:由于用户总数不多,即使出现冲突重试,性能开销也可以忽略不计

内容的提问来源于stack exchange,提问作者David Thielen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 11:30:28