【问题标题】:Is my use of ConcurrentQueue here between 2 threads ok?我在 2 个线程之间使用 ConcurrentQueue 可以吗?
【发布时间】:2010-07-21 04:58:51
【问题描述】:

我可以在 2 个线程之间使用 ConcurrentQueue 吗?我想检查我不需要明确地“锁定”任何地方。特别是看看我在 COMMENTS 中的行,我会在这里丢包吗...

public class PacketCapturer
{

private static ConcurrentQueue<Packet> _packetQueue = new ConcurrentQueue<Packet>();

public PacketCapturer(IPHostEntry proxyDns, ref BackgroundWorker bw)
{
    // start the background thread
    var backgroundThread = new System.Threading.Thread(BackgroundThread);
    backgroundThread.Start();

    // Start Packet Capture with callback via PacketCapturerCallback
}

private void PacketCapturerCallback(Packet packet)
{
    _packetQueue.Enqueue(packet);
}

private static void BackgroundThread()
{
    while (!BackgroundThreadStop)
    {
        if (_packetQueue.Count == 0) Thread.Sleep(250);
        else 
        {
            ConcurrentQueue<Packet> ourQueue;  
            ourQueue = _packetQueue;   // COULD I DROP A PACKET BETWEEN HERE
            _packetQueue = new ConcurrentQueue<Packet>();      // AND HERE???

            Console.WriteLine("BackgroundThread: ourQueue.Count is {0}", ourQueue.Count);
        }
    }
}

【问题讨论】:

  • 您永远不会同时使用队列(也就是说,您永远不会从第二个线程访问它),那么您为什么还要使用 ConcurrentQueue?

标签: c# .net multithreading concurrency


【解决方案1】:

不,不行。首先,如果您在并发线程中像这样更改引用,则 _packetQueue 必须 标记为 volatile 以防止编译器和代码生成优化永远不会看到更改。 _packetQueue 的 cahning 通常应作为 Interlocked.CompareExchange 发生,但这对您的使用不太重要。

但更令人担忧的是在后台线程中像这样更改 packetQueue 实例的模式。这样做的目的是什么?它有一种可怕的代码气味......

更新

我通常做的是:

生产者线程:

Producer () {
...
lock(_sharedQueue) {
  _sharedQueue.Enqueue(something);
}
...
}

消费者线程:

consumer (...) {
...
var Something[] toProcess = null;
lock(_sharedQueue)
{
  toProcess = _sharedQueue.Toarray();
   _sharedQueue.Clear();
}
// Process here the toProcess array
...
}

这对于我曾经有过的每一次使用都足够好。处理不会在锁定下发生,因此锁定是最小的。不需要花哨的 ConcurrentQueue,一个普通的旧 .Net 2.0 集合就足够了。通常我会使用专用的锁定对象,而不是锁定实际的队列实例。

【讨论】:

  • 你的目标是什么?您的目标是无锁队列吗?复制出元素不会是无锁的,ConcurrentQueue 在内部使用锁定。无论如何,实现无锁队列不是微不足道的,我非常怀疑你是否真的需要它。在具有 +64 个内核的系统中需要无锁结构并执行极其密集和时间关键的工作,例如 OS 调度程序。
  • 我只是在寻求一种真正解耦数据包捕获回调的方法,因此要保持回调本身非常短,并在另一个线程上使用数据
  • 附言。我刚刚尝试使用 ConcurrentQueue,然后在一个线程上使用 Enqueue 方法,在另一个线程上使用 TryDequeue 方法。乍一看似乎工作正常,但我想我必须做一些负载测试来证明它没问题。
  • 为什么不简单地 TryDequeue 和进程,一次一个元素?不需要其他任何东西,ConcurrentQueue 具有内部锁定保护。
【解决方案2】:

编辑:我认为 Mark/Gabe 是正确的,您不会丢失任何数据包。我会把剩下的留作参考,以防其他人对此发表意见。


很简单,是的。您可能会在那里丢失一个或多个数据包。您可能想查看 ConcurrentQueue 提供哪些方法来获取/删除其中的一部分,因为这看起来像您想要做的。

为什么不 TryDequeue 直到它返回 false:

    else 
    {
        Queue<Packet> ourQueue = new Queue<Packet>();  //this doesn't need to be concurrent unless you want it to be for some other reason.
        Packet p;
        while(_packetQueue.TryDequeue(out p))
        {
            ourQueue.Enqueue(p);
        }

        Console.WriteLine("BackgroundThread: ourQueue.Count is {0}", ourQueue.Count);
    }

【讨论】:

  • @Chad - 想解释一下如何“是”?
  • Marc... 我的意思是,他可能会丢失一个或多个数据包...方法如下: 线程 1:ourQueue = _packetQueue;线程 2:_packetQueue.Enqueue(packet); //这个数据包丢失了......更多队列可能发生在这里线程1:_packetQueue = new ConcurrentQueue();
  • 我可以同时从两 (2) 个线程中添加和删除的队列的概念对我来说似乎很好 - 那么 Queue 在这里支持这种并发使用吗?应该是 ConcurrentQueue 吗?
  • Greg,Queue 不支持开箱即用。我在示例中使用了队列,因为我添加的队列仅由 1 个线程添加。 _packetQueue 应该仍然是 ConcurrentQueue,但 ourQueue 可以是 Queue。也就是说,除非您需要 ourQueue 也可以跨多个线程工作。我不知道你的其余代码对 ourQueue 做了什么,所以我不能说任何一种方式。
  • 我和 Marc 在一起——我看不到数据包会被丢弃在哪里。在您的示例中,乍得,数据包不会丢失;他们最终会出现在ourQueue
【解决方案3】:

我认为你不能丢弃一个数据包,因为显然任何线程都会取消对旧的或新的的引用。边缘情况是,您在ourQueue 收到更多数据 你认为你已经交换了它们,或者你也可能最终没有注意到Sleep 循环中的参考变化(但是: 有2个线程,唯一改变引用的线程这个线程,所以不一定有问题。

TBH,如果您在这里有 2 个线程,为什么不直接使用 lock 或使用 ReaderWriterLock?弄清楚什么时候会发生什么会简单得多……

【讨论】:

  • Marc - 您对使用 lock 或 ReadWriterLock 或使用 Chad 给出的示例有何评论?
  • @Greg - 如果您没有 交换队列(出于某种原因),那么这是理想的。有了 Chad 的更新,这看起来不错。
猜你喜欢
  • 2014-03-01
  • 2018-10-24
  • 2012-01-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-14
  • 2021-03-03
  • 2021-10-25
相关资源
最近更新 更多