【问题标题】:Using C# ConcurrentBag<T> with multiple producers and a single consumer将 C# ConcurrentBag<T> 与多个生产者和单个消费者一起使用
【发布时间】:2015-06-16 11:47:47
【问题描述】:

我遇到了多个线程正在创建一个 ICollection 对象的情况。 ConcurrentBag 似乎是最好的(?)解决方案,因为 - 1)每个线程都有自己的本地队列,2)线程不需要通信 - 它们是独立的。到目前为止一切顺利,但事实是我需要从此方法返回一个 ISet(在所有生产者终止之后)。即使当前的 ConcurrentBag 实例 IS 不同(由于应用程序的逻辑而得到保证),我仍然需要将其转换为 ISet,例如 HashSet。在这一点上没有更多的生产者。现在真正的问题来了:

当 ConcurrentBag 被迭代时,调用线程会为不在线程本地队列中的 每个 项获取锁吗?或者它会在每个线程中获取一次锁吗?此外,仅迭代袋子和显式地、锁定地调用 bag.Distinct() 之间的内部实现是否存在差异?

【问题讨论】:

  • 要回答您问题的第二部分,迭代集合和调用Distinct 都使用相同的机制(即IEnumerable 实现),因此不应该有任何差异锁定-明智的
  • 没有锁,因为迭代 ConcurrentBag 是在副本上完成的。
  • @shay__ 为什么不为每个生产者创建一个本地 List&lt;T&gt;,并在所有生产者完成后汇总结果?
  • @Mateusz 所以——每个线程都会为调用线程创建一个快照来迭代?
  • Distinct() 在 ConcurrentBag 上调用 GetEnumerator(),因此实际上您将迭代两次。

标签: c# .net multithreading concurrency locking


【解决方案1】:

看看ConcurrentBag的源码:http://referencesource.microsoft.com/#System/sys/system/collections/concurrent/ConcurrentBag.cs,537a65e966c1c38d

遍历包会触发对FreezeBag 的调用。该方法调用AcquireAllLocks,浏览每个线程的队列并设置锁:

/// <summary>
/// local helper method to acquire all local lists locks
/// </summary>
private void AcquireAllLocks()
{
    Contract.Assert(Monitor.IsEntered(GlobalListsLock));

    bool lockTaken = false;
    ThreadLocalList currentList = m_headList;
    while (currentList != null)
    {
        // Try/Finally bllock to avoid thread aport between acquiring the lock and setting the taken flag
        try
        {
            Monitor.Enter(currentList, ref lockTaken);
        }
        finally
        {
            if (lockTaken)
            {
                currentList.m_lockTaken = true;
                lockTaken = false;
            }
        }
        currentList = currentList.m_nextList;
    }
}

它将每个线程获取一次锁,而不是每个项目一次。

迭代或调用Distinct 都会调用GetEnumerator 方法,所以没有区别。

【讨论】:

    猜你喜欢
    • 2014-12-09
    • 2016-09-11
    • 1970-01-01
    • 1970-01-01
    • 2011-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-07
    相关资源
    最近更新 更多