自定义SortedList线程安全Wrapper并发自增问题排查求助
问题分析与解决方案
核心问题原因
你遇到的问题本质是自增操作并非原子性:虽然Wrapper给索引器的get和set分别加了锁,但list[0] += 1这类操作会被拆分为三个独立步骤:
- 通过
get获取当前值(锁随即释放) - 计算自增后的值
- 通过
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
相关产品推荐
相关产品推荐

