问题 当Iterating Over ConcurrentDictionary并且只读取时,ConcurrentDictionary被锁定了吗?


  1. 我在我的Web应用程序中创建了一个ConcurrrentDictionary作为应用程序对象。它在会议之间共享。 (基本上用作存储库。)
  2. 有时,任何可用会话都会将新项添加到字典中。

仅允许管理员查看

现在,我想允许管理员列出字典中的所有值,但是 管理员不会添加或删除项目相反,我只会为管理员提供一种方法,通过遍历项目来阅读集合来查看项目。

(伪)代码看起来像这样

foreach (var e in EmployeeCache.Instance.AllEmployees)
{
     Console.WriteLine(e.Key);
}

我的问题是:

如果我遍历项目,ConcurrentDictionary在被读取时会被锁定吗?换句话说,ConcurrentDictionary是否被锁定,以便在管理代码只是通过ConcurrentDictionary迭代时,其他会话将无法添加或删除?

如果没有锁定,你能解释一下吗?

如果您认为它没有锁定,您能否快速总结一下它是如何做到的? 例如,它是否为只读操作创建ConcurrentDictionary的副本,然后允许读取迭代运行 - 理解不会看到对真实字典的并发更改?

我想要确定的是什么

我试图了解提供ConcurrentDictionary查看器的影响,该查看器可以经常由管理员刷新。 I.E.如果他们经常刷新它可能会影响Web应用程序的性能。当会话等待对象解锁时,他们可以添加/删除项目?


2711
2018-06-16 15:20


起源



答案:


这是怎么回事 ConcurrentDictionary.GetEnumerator 实施:

/// <remarks>
/// The enumerator returned from the dictionary is safe to use concurrently with
/// reads and writes to the dictionary, however it does not represent a moment-in-time 
/// snapshot of the dictionary. The contents exposed through the enumerator may contain 
/// modifications made to the dictionary after <see cref="GetEnumerator"/> was called.
/// </remarks>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
    Node[] buckets = m_tables.m_buckets;

    for (int i = 0; i < buckets.Length; i++)
    {
        // The Volatile.Read ensures that the load of the fields of 'current'
        // doesn't move before the load from buckets[i].
        Node current = Volatile.Read<Node>(ref buckets[i]);

        while (current != null)
        {
            yield return new KeyValuePair<TKey, TValue>(current.m_key, current.m_value);
            current = current.m_next;
        }
    }
}

如您所见,迭代是无锁的,只是产生一个不可变的结构(KeyValuePair)每次迭代返回给调用者。这就是为什么它无法保证时间的快照 ConcurrentDictionary

在迭代时,这肯定不会对添加/更新新值产生性能影响,但它无法保证您的管理员将看到字典的最新快照。

  1. 您可以自己浏览其余的源代码 http://sourceof.net
  2. 你也可以看看 在并发集合中: ConcurrentDictionary 作者:Simon Cooper。
  3. 所有新的并发集合都是无锁的吗?

9
2018-06-16 15:38



* Yuval,谢谢。这就是我想知道的。非常感谢您花时间阅读我的帖子并专注于我的要求。我要等了,但我会在接下来的日子里将其标记为答案。再次感谢您做出如此深思熟虑的反应。 - raddevus
为什么会有 Volatile.Read (记忆围栏) current = buckets[i] ?当然,有评论,但我不明白。 - tigrou
我找到了答案:在C#中,所有读取都是非易失性的,这意味着如果在一个线程中更改了一个变量,它可能仍然不会反映在另一个线程中(因为缓存/优化)。 Volatile.Read 确保检索到最新,最新鲜的价值。这是我们在阅读时想要的 buckets[i] - tigrou
有没有其他方法可以从dictiooutlockingout中读取任何用于修改的内容。我相信索引器也会读出锁定。 - eran otzap


答案:


这是怎么回事 ConcurrentDictionary.GetEnumerator 实施:

/// <remarks>
/// The enumerator returned from the dictionary is safe to use concurrently with
/// reads and writes to the dictionary, however it does not represent a moment-in-time 
/// snapshot of the dictionary. The contents exposed through the enumerator may contain 
/// modifications made to the dictionary after <see cref="GetEnumerator"/> was called.
/// </remarks>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
    Node[] buckets = m_tables.m_buckets;

    for (int i = 0; i < buckets.Length; i++)
    {
        // The Volatile.Read ensures that the load of the fields of 'current'
        // doesn't move before the load from buckets[i].
        Node current = Volatile.Read<Node>(ref buckets[i]);

        while (current != null)
        {
            yield return new KeyValuePair<TKey, TValue>(current.m_key, current.m_value);
            current = current.m_next;
        }
    }
}

如您所见,迭代是无锁的,只是产生一个不可变的结构(KeyValuePair)每次迭代返回给调用者。这就是为什么它无法保证时间的快照 ConcurrentDictionary

在迭代时,这肯定不会对添加/更新新值产生性能影响,但它无法保证您的管理员将看到字典的最新快照。

  1. 您可以自己浏览其余的源代码 http://sourceof.net
  2. 你也可以看看 在并发集合中: ConcurrentDictionary 作者:Simon Cooper。
  3. 所有新的并发集合都是无锁的吗?

9
2018-06-16 15:38



* Yuval,谢谢。这就是我想知道的。非常感谢您花时间阅读我的帖子并专注于我的要求。我要等了,但我会在接下来的日子里将其标记为答案。再次感谢您做出如此深思熟虑的反应。 - raddevus
为什么会有 Volatile.Read (记忆围栏) current = buckets[i] ?当然,有评论,但我不明白。 - tigrou
我找到了答案:在C#中,所有读取都是非易失性的,这意味着如果在一个线程中更改了一个变量,它可能仍然不会反映在另一个线程中(因为缓存/优化)。 Volatile.Read 确保检索到最新,最新鲜的价值。这是我们在阅读时想要的 buckets[i] - tigrou
有没有其他方法可以从dictiooutlockingout中读取任何用于修改的内容。我相信索引器也会读出锁定。 - eran otzap


这就是文档所说的:

从字典返回的枚举器可以安全使用   同时读取和写入字典,但它确实如此   不代表字典的即时快照。该   通过枚举器公开的内容可能包含所做的修改   调用GetEnumerator后到字典。

http://msdn.microsoft.com/en-us/library/dd287131(v=vs.110).aspx

因此,如果您想要“快照”行为,则必须复制Keys集合并迭代副本,否则您将迭代可变线程安全集合。


4
2018-06-16 15:27



当“快照”键不再存在时,这可能导致竞争条件,因此这不是真正的快照。 - urbanhusky