我在两个线程之间使用ConcurrentQueue吗?

时间:2010-07-21 04:58:52

标签: c# .net multithreading concurrency

我在两个线程之间使用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);
        }
    }
}

3 个答案:

答案 0 :(得分:2)

不,不行。首先,如果在并发线程中更改这样的引用,则_packetQueue 必须标记为volatile,以防止编译器和代码生成优化永远不会看到更改。 _packetQueue shoudl的更改通常作为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集合就足够了。通常我会使用专用的锁定对象,而不是锁定实际的队列实例。

答案 1 :(得分:1)

编辑:我认为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);
    }

答案 2 :(得分:1)

我认为您不能删除数据包,因为显然任何线程都会取消引用 旧的或新的。边缘情况是,在您认为已经交换了之后,您会在ourQueue 中获得更多数据,或者您最终也没有注意到{{1}中的参考更改} loop(但是:有2个线程,唯一更改引用的线程是这个线程,所以不一定是个问题)。

TBH,如果此处有2个主题,为什么不只是Sleep或使用lock?弄清楚......会发生什么事情会简单得多。