为什么c#迭代器使用互锁操作跟踪创建线程?

自从我读到Jon Skeet的
site上的迭代器以来,这就是令我困惑的事情.

Microsoft使用自动迭代器实现了一个简单的性能优化 – 返回的IEnumerable可以作为IEnumerator重用,从而节省了对象创建.现在因为IEnumerator必然需要跟踪状态,所以这仅在第一次迭代时才有效.

我无法理解的是为什么设计团队采取他们所采取的方法来确保线程安全.

通常,当我处于类似的位置时,我会使用我认为简单的Interlocked.CompareExchange – 以确保只有一个线程能够将状态从“可用”更改为“正在进行中”.

从概念上讲,它非常简单,只需一个原子操作,不需要额外的字段等.

但设计团队接近了吗?每个IEnumerable都保留一个创建线程的托管线程ID的字段,然后在针对该字段调用GetEnumerator时检查该线程ID,并且只有当它是相同的线程时,并且它是第一次被调用时,IEnumerable才能返回自身作为IEnumerator.似乎很难推理,imo.

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

或者是否有其他原因,可能涉及内存模型或ARM设备或我没有看到的东西?也许规范赋予了IEnumerable实现的特定要求?只是真的很困惑.

最佳答案 我不能肯定地回答,但至于你的问题:

Are Interlocked operations far slower than two calls to
System.Threading.Thread.CurrentThread.ManagedThreadId, so much so that
it justifies the extra field?

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

Understanding the Impact of Low-Lock Techniques in Multithreaded Apps开始:

Interlocked instructions need to ensure that caches are synchronized
so that reads and writes don’t seem to move past the instruction.
Depending on the details of the memory system and how much memory was
recently modified on various processors, this can be pretty expensive
(hundreds of instruction cycles).

Threading in C#年,它将开销列为10ns.而获取ManagedThreadId应该是静态数据的正常非锁定读取.

现在这只是我的推测,但如果你考虑正常的用例,那就是调用函数来检索IEnumerable并立即迭代它.所以在标准用例中,对象是:

>使用一次
>在创建它的同一个线程上使用
>短暂的

因此,这种设计不会带来同步开销,并且会牺牲4个字节,这可能只会在很短的时间内使用.

当然,要证明这一点,您必须进行性能分析,以确定相对成本和代码分析,以证明常见情况.

点赞