我正在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
}
}
如果它可以很容易地“修复”而没有副作用,这似乎很奇怪吗?
答案 0 :(得分:13)
ConcurrentQueue<T>
无锁,并使用旋转等待来实现高性能并发访问。实现只需要完成更多的工作以返回确切的计数,而不是检查是否没有项目,这就是建议IsEmpty
的原因。
直观地说,当没有其他客户端更新队列时,您可以认为Count
必须等待时间片,以便拍摄快照然后计算该快照中的项目。 IsEmpty
只需要检查是否至少有一个项目。并发Enqueue
和TryDequeue
操作正在更改计数,因此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
直接从字段中读取,那将会很好。
编辑我通过反射器检查了IsEmpty
和Count
的实现,两者都昂贵。显然,仅将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的并发结构的计数几乎没有意义,因为调用的结果可能没有用,并且大大改变了。