ConcurrentBag的预期用法和空虚作为终止条件

时间:2015-12-05 19:53:58

标签: c# multithreading concurrency

  

“ConcurrentBag(T)是一个线程安全的包实现,针对。进行了优化   同一个线程将生成和消费的场景   数据存储在包中。“ - MSDN

我有这个确切的用例(多个线程都消耗和生产),但是我需要能够及时有效地确定包永远是空的(我的线程只根据消耗的东西产生,并且在启动线程之前,bag使用单个元素进行快速启动。)

我无法找到一种没有全局锁定的无竞争条件的有效方法来做到这一点。我相信引入全局锁会否定使用大多数无锁的ConcurrentBag的好处。

我的实际用例是一个“无序”(二进制)树遍历。我只需要访问每个节点,并为每个节点做一些非常轻的计算。我不关心他们被访问的顺序。当所有节点都被访问时,算法应该终止。

int taskCount = Environment.ProcessorCount;
Task[] tasks = new Task[taskCount];
var bag = new ConcurrentBag<TreeNode>();
bag.Add(root);
for (int i = 0; i < taskCount; i++)
{
    int threadId = i;
    tasks[threadId] = new Task(() =>
    {
        while(???) // Putting bag.IsEmpty>0 here would be obviously wrong as some other thread could have removed the last node but not yet added the node's "children"
        {
            TreeNode node;
            bool success = bag.TryTake(out node);

            if (!success) continue; //This spinning is probably not very clever here, but I don't really mind it.

            // Placeholder: Do stuff with node

            if (node.Left != null) bag.Add(node.Left);
            if (node.Right != null) bag.Add(node.Right);
        }
    });
    tasks[threadId].Start();
}
Task.WaitAll(tasks);

那么如何为此添加有效的终止条件呢?我不介意当行李接近空时情况会变得昂贵。

1 个答案:

答案 0 :(得分:2)

之前我遇到过这个问题。在检查队列之前,我让线程注册为处于等待状态。如果队列为空并且所有其他线程也在等待,那么我们就完成了。如果其他线程仍然忙,那么黑客入侵,睡眠时间为10毫秒。我相信有可能在不等待使用某种同步(也许Barrier)的情况下解决这个问题。

代码是这样的:

string Dequeue()
{
    Interlocked.Increment(ref threadCountWaiting);
    try
    {
        while (true)
        {
            string result = queue.TryDequeue();
            if (result != null)
                return result;

            if (cancellationToken.IsCancellationRequested || threadCountWaiting == pendingThreadCount)
            {
                Interlocked.Decrement(ref pendingThreadCount);
                return null;
            }

            Thread.Sleep(10);
        }
    }
    finally
    {
        Interlocked.Decrement(ref threadCountWaiting);
    }
}

可以用Barrier替换睡眠和计数器维护。我只是没有打扰,这已经很复杂了。

Interlocked操作是可伸缩性瓶颈,因为它们基本上是使用硬件自旋锁实现的。因此,您可能希望在方法的开头插入快速路径:

            string result = queue.TryDequeue();
            if (result != null)
                return result;

大部分时间都会采取快速路径。