2011-11-28 52 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