ConcurrentQueue
有TryDequeue
方法。
Queue
只有Dequeue
方法。
在ConcurrentDictionary
中没有Add
方法,但我们改为TryAdd
。
我的问题是:
这些并发收集方法之间的差异是什么?为什么它们对于并发集合不同?
答案 0 :(得分:3)
语义不同。
Queue.Dequeue
失败通常表示内部应用程序逻辑存在问题,因此在这种情况下抛出异常会很好。
然而,ConcurrentQueue.TryDeque
的失败是常规流程中可能出现的问题,因此避免异常并返回Boolean
是处理它的合理方法。
ConcurrentQueue<T>
在内部处理所有同步。如果两个线程在同一时刻调用TryDequeue
,则两个操作都不会被阻止。当在两个线程之间检测到冲突时,一个线程必须再次尝试检索下一个元素,并在内部处理同步。
(.NET框架中常见的做法是Try...
函数返回布尔结果而不是抛出,参见例如TryParse
方法。)
答案 1 :(得分:3)
这些方法被赋予Try
语义的原因是,在设计上,没有办法可靠地告诉Dequeue
或Add
操作会成功。
当队列不是并发时,您可以在调用Dequeue
方法之前检查是否有任何要出队的内容。同样,您可以检查非并发Dictionary
中的密钥是否存在。你不能对并发类做同样的事情,因为有人可能会在你检查它之后将你的项目出列,但是在你实际出列之前。换句话说,Try
操作允许您检查前提条件并执行原子操作。
另一种方法是让你出队或添加,并在操作失败时抛出异常,就像非并发实现一样。这种方法的缺点是在并发类中完全需要非并发类中的这些异常情况,因此对它们使用异常处理将是错误的。
答案 2 :(得分:3)
使用Dictionary<TKey, TValue>
时,假设您要实现自己的逻辑,以确保不输入重复的密钥。例如,
if(!myDictionary.ContainsKey(key)) myDictionary.Add(key, value);
但是当我们有多个线程进行时我们使用Concurrent集合,并且它们可能都在尝试同时修改字典。
如果两个线程同时尝试执行上面的代码,那么myDictionary.ContainsKey(key)
可能会为两个线程返回false,因为它们同时都在检查并且该键尚未添加。然后他们都试图添加密钥,一个失败。
有人在阅读那些不知道它是多线程的代码时可能会感到困惑。我检查确保密钥不在字典之前我添加了它。那我怎么得到一个例外?
ConcurrentDictionary.TryAdd
通过允许您“尝试”添加密钥来解决这个问题。如果它添加了值,则返回true
。如果不是,则返回false
。但不会做的是与另一个TryAdd
发生冲突并抛出异常。
您可以通过将Dictionary
包装在一个类中并在其周围放置lock
语句来确保每次只有一个线程进行更改,从而完成所有这些操作。 ConcurrentDictionary
只为你做到这一点并且做得非常好。您不必查看其工作原理的所有细节 - 您只需使用它就知道已经考虑了多线程。
这是在多线程应用程序中使用类时要查找的详细信息。如果您转到ConcurrentDictionary Class的文档并滚动到底部,您会看到:
线程安全
所有公开和受保护的成员 ConcurrentDictionary是线程安全的,可以使用 同时来自多个线程。但是,成员通过访问 ConcurrentDictionary的接口之一 实现,包括扩展方法,不保证是 线程安全,可能需要由调用者同步。
换句话说,多个线程可以安全地读取和修改集合。
在Dictionary Class下,您会看到:
线程安全
字典可以支持多个 只要集合未被修改读者, 。甚至 因此,通过集合枚举本质上不是一个 线程安全的程序。 在枚举竞争的罕见情况下 使用写访问时,必须在整个期间锁定该集合 枚举。允许多个访问集合 阅读和写作的线程,你必须实现自己的 同步。
多个线程可以读取密钥,但是如果多个线程要 write ,那么您需要以某种方式lock
字典来确保一次只有一个线程尝试更新。< / p>
Dictionary<TKey, TValue>
公开了一个Keys
集合和Values
集合,因此您可以枚举键和值,但它会警告您如果另一个线程要修改它,则不要尝试这样做字典。在添加或删除项目时,您无法枚举某些内容。如果需要遍历键或值,则必须锁定字典以防止在该迭代期间进行更新。
ConcurrentDictionary<TKey, TValue>
假设将有多个线程读取和写入,因此它甚至不会公开键或值集合以供您枚举。
答案 3 :(得分:2)
由于这些集合旨在同时使用,因此您不能依赖于以顺序方式检查前置条件,而是需要进行原子操作。
以字典为例,通常你可以编写这样的代码:
if (!dictionary.ContainsKey(key))
{
dictionary.Add(key, value);
}
在多个线程使用相同字典的情况下,另一个线程完全有可能在您检查ContainsKey
和调用Add
之间插入一个具有相同键的值。< / p>
TryAdd
解决了这个问题,因为它会成功还是失败,具体取决于密钥是否存在。
答案 4 :(得分:0)
来自MSDN:
尝试删除并返回对象的开头 并发队列。
返回
如果一个元素被删除并从该元素的开头返回,则为true ConcurrentQueue成功;否则,错误。
因此,如果你可以删除TryDequeue
只是重新转发并返回它,如果不能返回false,你知道在queeue空闲时再试一次。