C# 中对同步集合的非锁定访问行为

Posted

技术标签:

【中文标题】C# 中对同步集合的非锁定访问行为【英文标题】:Behavior of non-locked access to synchronized collection in C# 【发布时间】:2020-07-22 18:01:22 【问题描述】:

我有一个可以被多个线程访问或修改的私有集合(例如ISet<int>)。

我的逻辑是这个线程只需要在某些状态下改变ISet,而另一个线程总是需要改变它。另一个线程将始终采用lock,在ISet 上实现lock 关键字,如下所示:

lock (this._set)

    // some mutating operation on this._set

对于这个线程,如果ISet 包含一个称为currentValueint,我只需要执行一个改变线程的操作,这在这种情况下很少见。另一个线程中的操作很频繁。因此问题是我是否可以在获取lock 之前检查ISet 是否包含currentValue,以尽量减少该线程阻塞另一个线程的时间,如下所示:

if (this._set.Contains(currentValue))

    lock (this._set)
    
            if (this._set.Contains(currentValue))
            
                // some mutating operation on this._set
            
    

问题是lock 之外的检查行为是否未定义,或者是否会导致ISet 进入未定义或意外状态。当然返回的实际值不能被信任使用,这就是为什么在lock 中再次检查它的原因,但这是我可以期望工作的有效优化,还是会弄乱集合的某些内部状态?目标是尽可能少地使用此线程中的lock

【问题讨论】:

ConcurrentDictionary 是否可以满足您的需求? 当你说集合可以被多个线程访问或修改,你的意思是集合是线程安全的吗? 【参考方案1】:

如果您的意思是这样,仅读取集合值不会引发异常或更改集合的值。它可能会返回脏数据,但您已经说过您期望这种可能性。我相信从像这样的非线程安全集合中读取甚至可以给你不完整的数据,或者半旧状态半新状态的数据(参见C# multi-threading: Acquire read lock necessary?的回复

假设_set.Contains(currentValue) 的第一个测试返回true,之后另一个线程立即更新集合,删除currentValue。然后你输入锁,但现在当你测试_set.Contains(currentValue)时,它返回false。

问题是这个线程改变集合有多重要?您的代码可能会错过每次更改集合的机会。您的优化最终可能会产生相反的效果 - 通过不必要的锁定减慢代码速度。

在不了解其他要求的情况下很难为您提供一个好的替代方案,但我想我已经回答了您的问题并表明代码实际上根本没有优化,但它本身不会破坏任何东西。

【讨论】:

以上是关于C# 中对同步集合的非锁定访问行为的主要内容,如果未能解决你的问题,请参考以下文章

C# 在多线程环境中,进行安全遍历操作

如何在 C# 中对数组执行集合减法?

C# Monitor:锁定资源

集合的同步与非同步

在不锁定集合的情况下从通用集合中获取 Count 值是不是安全?

互斥锁 - 可以通过合并构建集合