簡體   English   中英

如何正確執行Parallel.ForEach,鎖定和進度報告

[英]How to do proper Parallel.ForEach, locking and progress reporting

我正在嘗試實現Parallel.ForEach模式並跟蹤進度,但我遺漏了一些有關鎖定的內容。 以下示例在threadCount = 1時計數到1000,但在threadCount > 1時不threadCount 。正確的方法是什么?

class Program
{
   static void Main()
   {
      var progress = new Progress();
      var ids = Enumerable.Range(1, 10000);
      var threadCount = 2;

      Parallel.ForEach(ids, new ParallelOptions { MaxDegreeOfParallelism = threadCount }, id => { progress.CurrentCount++; });

      Console.WriteLine("Threads: {0}, Count: {1}", threadCount, progress.CurrentCount);
      Console.ReadKey();
   }
}

internal class Progress
{
   private Object _lock = new Object();
   private int _currentCount;
   public int CurrentCount
   {
      get
      {
         lock (_lock)
         {
            return _currentCount;
         }
      }
      set
      {
         lock (_lock)
         {
            _currentCount = value;
         }
      }
   }
}

從多個線程(共享count變量)調用count++類的常見問題是這一系列事件可能發生:

  1. 線程A讀取count的值。
  2. 線程B讀取count的值。
  3. 線程A遞增其本地副本。
  4. 線程B遞增其本地副本。
  5. 線程A將遞增的值寫回count
  6. 線程B將遞增的值寫回count

這樣,線程A寫入的值被線程B覆蓋,因此該值實際上只增加一次。

您的代碼會在操作1,2( get )和5,6( set )周圍添加鎖定,但這對阻止有問題的事件序列沒有任何作用。

你需要做的是鎖定整個操作,這樣當線程A遞增值時,線程B根本無法訪問它:

lock (progressLock)
{
    progress.CurrentCount++;
}

如果您知道只需要遞增,則可以在Progress上創建一個封裝此方法的方法。

老問題,但我認為有更好的答案。

您可以使用Interlocked.Increment(ref progress)報告進度,這樣您就不必擔心將寫入操作鎖定為進度。

最簡單的解決方案實際上是用字段替換屬性,並且

lock { ++progress.CurrentCount; }

(我個人更喜歡在增量后增加前體的外觀,因為“++。”的東西在我的腦海中發生沖突!但是后增量當然會起作用。)

這將具有減少開銷和爭用的額外好處,因為更新字段比調用更新字段的方法更快。

當然,將其封裝為屬性也具有優勢。 IMO,因為字段和屬性語法是相同的,當屬性自動實現或等效時,在字段上使用屬性的唯一好處是,當您有一個場景,您可能希望部署一個程序集而不必構建和部署依賴項重新組裝。 否則,您也可以使用更快的字段! 如果需要檢查值或添加副作用,您只需將字段轉換為屬性並再次構建。 因此,在許多實際情況中,使用場不會受到懲罰。

然而,我們生活在一個許多開發團隊教條操作的時代,並使用StyleCop等工具來強化他們的教條主義。 與編碼器不同,這些工具不夠聰明,無法判斷何時使用字段是可以接受的,因此“即使是StyleCop檢查也很簡單的規則”變成“將字段封裝為屬性”,“不使用公共字段”等等...

從屬性中刪除鎖定語句並修改主體:

 object sync = new object();
        Parallel.ForEach(ids, new ParallelOptions {MaxDegreeOfParallelism = threadCount},
                         id =>
                             {
                                 lock(sync)
                                 progress.CurrentCount++;
                             });

這里的問題是++不是原子的 - 一個線程可以讀取並遞增讀取值的另一個線程之間的值,並存儲(現在不正確的)遞增值。 事實上,包含你的int的屬性可能更加復雜。

例如

Thread 1        Thread 2
reads 5         .
.               reads 5
.               writes 6
writes 6!       .

setter和getter周圍的鎖沒有幫助,因為沒有什么可以阻止lock它們被無序調用。

通常,我建議使用Interlocked.Increment ,但你不能將它與屬性一起使用。

相反,你可以公開_lock並讓lock塊圍繞progress.CurrentCount++; 呼叫。

最好將任何數據庫或文件系統操作存儲在本地緩沖區變量中,而不是將其鎖定。 鎖定會降低性能。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM