2011-11-28 57 views
4

自从我阅读Jon Skeet的site上的迭代器后,这只是让我感到困惑。为什么c#迭代器使用互锁操作跟踪创建线程?

微软用自动迭代器实现了一个简单的性能优化 - 返回的IEnumerable可以作为IEnumerator重用,保存对象的创建。现在因为IEnumerator需要跟踪状态,所以只有在第一次迭代时才有效。

我不明白的是为什么设计团队采取了他们确保线程安全的方法。

通常当我处于类似的位置时,我会使用我认为是简单的Interlocked.CompareExchange--确保只有一个线程设法将状态从“可用”更改为“正在处理”。

概念上它是非常简单的,单一的原子操作,不需要额外的字段等等

但设计团队的做法?每个IEnumerable都保留创建线程的托管线程ID的字段,然后在调用GetEnumerator时检查该线程ID是否对该字段进行检查,并且只有它是相同的线程,并且它是第一次调用时,IEnumerable是否可以返回自身作为IEnumerator。这似乎很难推理,伊莫。

我只是想知道为什么采取这种方法。 Interlocked操作比两次调用System.Threading.Thread.CurrentThread.ManagedThreadId要慢得多,以至于证明额外的字段是正确的?

还是有其他原因背后,也许涉及内存模型或ARM设备或我没有看到?也许这个规范给IEnumerable的实现提供了特定的要求?只是真正困惑。

回答

2

我不能definatively回答,但你的问题:

联锁操作远比两次调用 System.Threading.Thread.CurrentThread.ManagedThreadId慢,以至于 它证明额外的领域?

是的互锁操作要慢得多,两次调用ManagedThreadId - 互锁操作并不便宜,因为它们需要多CPU系统来同步缓存。

Understanding the Impact of Low-Lock Techniques in Multithreaded Apps

互锁指令需要确保缓存同步 使读取和写入似乎不搬过去的指令。 根据内存系统的细节以及最近在各种处理器上修改了多少内存 ,这可能是非常昂贵的(数百个指令周期) 。

Threading in C#中,它列出开销的开销为10ns。而获得ManagedThreadId应该是一个正常的非锁定静态数据读取。

现在这只是我的猜测,但如果您考虑正常使用情况,它将调用函数来检索IEnumerable并立即迭代一次。所以在标准使用情况的对象是:

  1. 使用一次
  2. 用它创建
  3. 短住

所以这样的设计带来了不同步开销和牺牲在同一线程上4个字节,可能只会在很短的时间内使用。

当然,为了证明这一点,您必须执行性能分析来确定相对成本和代码分析,以证明常见情况。

+0

这很有道理,谢谢。我想我的印象是联锁操作仍然相对便宜 - 我一直认为分配(即首先是IEnumerable)必须至少包含一个锁定的加法。如果它们非常昂贵,我现在意识到每个线程都可能被分配到一个小池中,以便不需要互锁操作等等。我也错误地认为Thread.CurrentThread可能不是最便宜的程序 - 尽管VS不会让我介入它,我愿意接受它很快。 Ty :) – Mania