广告期间,有界阻止收藏可能会丢失数据

时间:2012-10-31 03:48:50

标签: c# multithreading parallel-processing concurrentdictionary blockingcollection

我有一个BlockingCollection(ConcurrentBag,50000),我试图为生产者线程使用一个非常小的有界容量50,000,以便最大化我可以在我的消费者线程的ConcurrentDictionary中处理的记录数。生产者比消费者快得多,并且会消耗大部分内存。

不幸的是,我立刻注意到我的ConcurrentDictionary中的记录总数现在大大低于在我的测试数据执行时添加有限容量50,000后的记录。我读到BlockingCollection的.add方法应该无限期地阻塞,直到集合中有空间来执行add。但是,情况似乎并非如此。

问题:

  1. 如果在BlockingCollection中的容量释放之前调用了太多的add,那么BlockingCollection的.add方法最终是否会超时或无声地失败?

  2. 如果对#1的回答是肯定的,那么在超出限制容量而不丢失数据后我可以尝试多少次添加?

  3. 如果调用了许多正在等待/阻塞容量并且调用了CompleteAdding()方法的BlockingCollection .add()方法,那些等待/阻塞添加会继续等待,然后最终添加或者它们默默地失败?

1 个答案:

答案 0 :(得分:9)

如果您正在使用BlockingCollection和ConcurrentDictionary,请确保您的代码中没有隐藏的BlockingCollection.TryAdd(myobject)方法,并将其误认为是ConcurrentDictionary.TryAdd()方法。如果已超过BlockingCollection的边界容量,则BlockingCollection.TryAdd(myobject)将返回false并丢弃生成“静默失败”的添加请求。

  1. BlockingCollection的.Add()方法似乎没有“静默失败”或在超出限制容量后大量丢失添加。如果过多的.add()等待添加到容量过大的BlockingCollection,则add()方法最终会导致进程耗尽内存。 (这必须是流量控制问题的极端情况)
  2. 见#1。
  3. 我自己的测试似乎表明,一旦调用了CompleteAdding()方法,所有后续添加都会失败,如MSDN文档中所述。
  4. 关于效果的最终说明

    与在同一进程中不使用Bounding Capacity和.TryAdd()相比,在BlockingCollection上使用Bounding Capacity和.Add()似乎(在我自己的情况下无论如何)。

    通过实施自己的边界容量策略,我获得了更好的性能结果。有很多方法可以做到这一点。三个选项包括与Monitor.PulseAll()一起使用的Thread.Sleep(),Thread.Spinwait()或Monitor.Wait()。当使用其中一个策略时,也可以使用BlockingCollection.TryAdd()而不是BlockingCollection.Add()并且没有限制容量而不会丢失任何数据或内存不足。这种方法似乎也能产生更好的性能。

    您可以根据哪种方案最适合生产者和消费者线程中的速度差异,从三个示例中进行选择。

    Thread.Wait()示例:

    //Check to see if the BlockingCollection's bounded capacity has been exceeded.
    while (Tokens.Count > 50000)
    {   //If the bounded capacity has been exceeded
        //place the thread in wait mode 
        Thread.Sleep(SleepTime);
    }
    

    Thread.SpinWait()示例:

    //Check to see if the BlockingCollection's bounded capacity has been exceeded.
    while (Tokens.Count > 50000)
    {   //If the capacity has been exceeded
        //place the thread in wait mode 
        Thread.SpinWait(SpinCount);
    }  
    

    Monitor.Wait()示例

    此示例在Producer和Consumer方面都需要一个钩子。

    制片人代码

    //Check to see BlockingCollection capacity has been exceeded.
    if (Tokens.Count > 50000)
    { 
        lock (syncLock)
        {   //Double check before waiting
            if (Tokens.Count > 50000)
            {
                Monitor.Wait(syncLock, 1000);
            }
        }
    }
    

    消费者代码

    //Check to see BlockingCollection capacity is back a normal range.
    if (Tokens.Count <= 40000)
    { 
        lock (syncLock)
        {   //Double check before waiting
            if (Tokens.Count < 40000)
            {
                Monitor.PulseAll(syncLock);
            }
        }
    }