2012-06-07 49 views
3

在My Parallel.ForEach循環中,localFinally委託在所有線程上都會被調用。 我發現這是因爲我的平行環路失速而發生的。 在我的並行循環中,我有大約三個在循環完成之前返回的條件檢查階段。而且它似乎是線程從這些階段返回而不是執行整個主體時它不會執行localFinally委託。儘管完成了所有迭代,但並行局部最終會失速

環路結構如下:

var startingThread = Thread.CurrentThread; 
Parallel.ForEach(fullList, opt, 
     ()=> new MultipleValues(), 
     (item, loopState, index, loop) => 
     { 
      if (cond 1) 
       return loop; 
      if (cond 2) 
       { 
       process(item); 
       return loop; 
       } 
      if (cond 3) 
       return loop; 

      Do Work(item); 
      return loop; 
      }, 
      partial => 
      { 
       Log State of startingThread and threads 
      }); 

我已經運行在一個小數據集的循環,並詳細記錄,並發現,雖然Parallel.ForEach完成所有迭代和日誌在最後本地最後的線程是 - 線程6的調用線程狀態是WaitSleepJoin循環指令16
循環仍然沒有完成並保持停頓...任何線索爲什麼攤位?

乾杯!

+0

可能是某處的死鎖 – Alex

+0

可能只是僞代碼,但cond 3在其當前狀態下永遠不會達到。 if(cond2)在條件周圍沒有括號(所以只有過程(項目)屬於它)。 –

+0

@RobertVerpalen沒有,這只是由於忽略括號而導致的僞代碼中的錯誤... –

回答

1

在看到localFinally的定義(在每個線程完成後執行)之後,我做了一個快速測試運行,這讓我懷疑這可能意味着並行性創建的線程少於執行的循環。例如

 var test = new List<List<string>>(); 
     for (int i = 0; i < 1000; i++) 
     { 
      test.Add(null); 
     } 

     int finalcount = 0; 
     int itemcount = 0; 
     int loopcount = 0; 

     Parallel.ForEach(test,() => new List<string>(), 
      (item, loopState, index, loop) => 
      { 
       Interlocked.Increment(ref loopcount); 
       loop.Add("a"); 
       //Thread.Sleep(100); 
       return loop; 
      }, 
      l => 
      { 
       Interlocked.Add(ref itemcount, l.Count);      
       Interlocked.Increment(ref finalcount);      
      }); 

在該循環結束時,ITEMCOUNT和loopcount 1000個預期,並且finalcount 1或2(我的機器上),這取決於執行速度。在有條件的情況下:直接返回時,執行速度可能更快,並且不需要額外的線程。只有在執行dowork時需要更多的線程。然而參數(在我的情況下是l)包含所有執行的組合列表。 這可能是記錄差異的原因嗎?

+0

不應該使用'Interlocked.Increment'和'Interlocked.Add'方法來避免可能與各種計數器產生線程競爭的情況? –

+0

在我的本地測試場景中它們是不穩定的,所以它們應該是線程安全的並且結果是相同的,但是對於發佈的示例,您應該使用某種鎖定 –

+3

@RobertVerpalen將字段標記爲volatile神奇地使它線程安全。特別是,這並不意味着'++'將會正常工作,因爲'++'不是原子的。 – svick

1

我想你剛纔誤解了localFinally的意思。它不是針對每個項目調用的,它是由Parallel.ForEach()使用的每個線程調用的。許多項目可以共享相同的線程。

它存在的原因是您可以在每個線程上獨立執行一些聚合,並且只在最後將它們連接在一起。這樣,你只能在一小段代碼中處理同步(並影響你的性能)。

例如,如果要計算分數的總和項目的集合,你可以做這樣的:

int totalSum = 0; 
Parallel.ForEach(
    collection, item => Interlocked.Add(ref totalSum, ComputeScore(item))); 

但在這裏,你叫Interlocked.Add()對於每一個項目,這可能會很慢。使用localInitlocalFinally,你可以重寫代碼:

int totalSum = 0; 
Parallel.ForEach(
    collection, 
    () => 0, 
    (item, state, localSum) => localSum + ComputeScore(item), 
    localSum => Interlocked.Add(ref totalSum, localSum)); 

注意,代碼只有在localFinally使用Interlocked.Add(),並在不訪問body全局狀態。這樣一來,同步成本只支付幾次,每次使用的線程只支付一次。

注意:在本例中我使用了Interlocked,因爲它非常簡單,顯然是正確的。如果代碼更復雜,我會首先使用lock,並且只有在需要良好性能時才嘗試使用Interlocked

+0

非常感謝Enlightening響應......在我的實現中,除了用於調試目的外,本地最終沒有任何用處,因爲我的parallel.ForEach循環執行所有迭代但仍然停滯。這是來自localFinally的Log輸出,它在調用Loop之前打印CurrentThread的狀態 - 調用線程狀態爲Thread 6 Loop Indx 16的WaitSleepJoin,但儘管完成了所有迭代,但Parallel.ForEach並沒有優雅地退出任何線索,爲什麼循環不會優雅地終止? –

+0

非常感謝您的回覆!我設法找到了代碼在其中一個調用中阻塞的位置,並且我得到了Loop進度完成!乾杯! –

相關問題