为什么ConcurrentQueue <t> .Count在IsEmpty == true时返回0?</t>

时间:2011-02-27 11:51:58

标签: c# .net collections

我正在James Michael Hare's blog上阅读.NET 4中的新并发集合类,page talking about ConcurrentQueue<T>说:

  

然而,仍然建议这样做   对于空检查,您调用IsEmpty   而不是将Count比较为零。

我很好奇 - 如果有理由使用IsEmpty而不是将Count与0进行比较,为什么该类在内部检查IsEmpty并在执行任何昂贵的工作之前返回0

E.g:

public int Count
{
    get
    {
        // Check IsEmpty so we can bail out quicker
        if (this.IsEmpty)
            return 0;

        // Rest of "expensive" counting code
    }
}

如果它可以很容易地“修复”而没有副作用,这似乎很奇怪吗?

4 个答案:

答案 0 :(得分:13)

ConcurrentQueue<T>无锁,并使用旋转等待来实现高性能并发访问。实现只需要完成更多的工作以返回确切的计数,而不是检查是否没有项目,这就是建议IsEmpty的原因。

直观地说,当没有其他客户端更新队列时,您可以认为Count必须等待时间片,以便拍摄快照然后计算该快照中的项目。 IsEmpty只需要检查是否至少有一个项目。并发EnqueueTryDequeue操作正在更改计数,因此Count必须重试;除非队列在空状态和非空状态之间转换,否则IsEmpty的返回值不会因并发操作而改变,因此不必等待。

我编写了一个简单的多线程测试应用程序,该应用程序显示Count慢了约20%(同时存在争用且没有争用);但是,这两种属性每秒可以调用数百万次,因此在实践中任何性能差异都可能完全忽略不计。

答案 1 :(得分:7)

让我向您展示夸大示例:

public bool IsEmpty
{
   get { /* always costs 10s here */  }
}

public int Count
{
   get { /* always costs 15s here, no matter Count is 0 or 1 or 2... */  }
}

如果他们像这样实现Count属性:

public int Count
{
   get
   {
       if (IsEmpty) return 0;
       //original implementation here
   }
}

现在,当最终的Count为0时,它花费10s(比之前少5s!太棒了!),但是对于那些Count是1/2 /更多,它比以前花费更多,因为检查IsEmpty需要花费时间!所以优化不是一个好主意,因为IsEmpty需要时间。如果IsEmpty直接从字段中读取,那将会很好。

编辑我通过反射器检查了IsEmptyCount的实现,两者都昂贵。显然,仅将IsEmpty检查为0计数会降低平均性能速度。

答案 2 :(得分:0)

IsEmpty提供了一些线程并发性,如果你获得Count值并比较它,它就在你的线程上,但是队列可以被改变。

MSDN说:

  

用于确定是否收藏   包含任何项目,使用此   建议使用属性而不是   从中检索项目数   Count属性并将其与之比较   0.但是,由于此集合旨在同时访问,   可能是另一个线程的情况   将在之后修改集合   IsEmpty返回,从而使   结果

答案 3 :(得分:0)

了解并发结构的工作原理非常重要。

if (isEmpty()) ...//do whatever

如果你有并发结构,那么检查就接近no-op,因为所有都可以在isEmpty和任何后续操作之间改变。

Count遍历节点(未使用c#将近6年,但java模拟器做同样的事情)进行计算,因此这是一个昂贵的调用。 直接回答:在Count之前检查isEmpty将产生额外的内存栅栏并有效地实现任何效果。 编辑:如果不清楚。队列为空时计算的成本与isEmpty完全相同,但是当队列不是时,它会花费很多!

类似于isEmpty的并发结构的计数几乎没有意义,因为调用的结果可能没有用,并且大大改变了。