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

自定义SortedList线程安全Wrapper并发自增问题排查求助

问题分析与解决方案

核心问题原因

你遇到的问题本质是自增操作并非原子性:虽然Wrapper给索引器的get和set分别加了锁,但list[0] += 1这类操作会被拆分为三个独立步骤:

  1. 通过get获取当前值(锁随即释放)
  2. 计算自增后的值
  3. 通过set写入新值(再次加锁)

两个线程会同时执行第一步,拿到相同的初始值(比如0),各自计算出1后再分别写入,最终结果被覆盖为1。Debug模式下调试器会降低线程切换频率,竞态条件很难触发,所以测试能通过;但正常运行时线程调度更频繁,问题就会复现。

正确解决方案

必须把获取值、计算、写入的整个逻辑放在同一个锁块内,保证操作的原子性。需要在Wrapper中添加专门的原子更新方法,而非依赖单独加锁的索引器。

方案1:数值类型自增专用方法

如果SortedList存储的是数值类型(如int、long等),可以直接添加自增方法:

public void IncrementAt(int index) where T : struct, IIncrementOperators<T>
{
    lock (_lock)
    {
        _sortedList[index]++;
    }
}

调用时用list.IncrementAt(0)替代list[0] += 1,整个自增过程在锁保护下完成,不会出现竞态。

方案2:通用原子更新方法

如果需要支持更灵活的更新逻辑(如自减、自定义计算),可以添加接受更新委托的通用方法:

public T UpdateAt(int index, Func<T, T> updateFunc)
{
    if (updateFunc == null)
        throw new ArgumentNullException(nameof(updateFunc));
    
    lock (_lock)
    {
        var currentValue = _sortedList[index];
        var updatedValue = updateFunc(currentValue);
        _sortedList[index] = updatedValue;
        return updatedValue;
    }
}

调用时传入更新逻辑:

list.UpdateAt(0, x => x + 1);

任何基于当前值的更新操作,都会在同一个锁块内完成,彻底避免竞态条件。

额外注意事项

确保Wrapper中_lock是初始化时就缓存的SyncRoot实例,不要在每次调用get/set时重新获取,避免出现锁实例不一致的问题。

内容的提问来源于stack exchange,提问作者Ts-Pytham

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 17:30:22