2013-02-19 43 views
19

看到代表通過並行的foreach做了以下工作的併發性能分析:爲什麼我在這裏有鎖?

enter image description here

循環中的每個線程從DB和過程讀取裏面的數據。線程之間沒有鎖,因爲每個線程都處理不同的數據。

由於未知原因(請參見黑色垂直矩形),看起來像foreach的所有線程中存在週期性鎖定。如果您看到選定的鎖定段(深紅色),您將看到堆棧顯示鎖定在StockModel.Quotation構造函數中的線程。那裏的代碼只是構造了兩個空列表!

我讀的地方,這可能是由GC引起的,所以我已經改變了垃圾收集與服務器模式下運行:

<runtime> 
    <gcServer enabled="true"/> 
</runtime> 

我有一個小的改進(約10% - 15 %更快),但我仍然有各處的垂直鎖。

我也添加到所有數據庫查詢的WITH(NOLOCK),因爲我只讀數據沒有任何區別。

任何暗示這裏發生了什麼?

已完成分析的計算機有8個內核。

編輯:使微軟符號服務器後,結果證明,所有線程被阻塞像wait_gor_gc_done或WaitUntilGCComplete電話。我認爲啓用GCServer我有一個GC爲每個線程,所以我會避免「垂直」鎖,但似乎並非如此。我錯了嗎?第二個問題:由於機器沒有受到內存壓力(使用8個演出中的5個),有沒有辦法延遲GC執行或暫停它,直到並行foreach結束(或將其配置爲不太經常觸發)?

+0

在你分配對象和鎖的很多情況下確實是由GC引起的,你有沒有嘗試剛剛起步的第三方物流工作之前強制GC.Collect的? GC.Collect與GCCollectionMode.Forced。 – Alex 2013-02-19 16:25:27

+0

那麼,在循環內部,我將分配大量在每次迭代結束時「放棄」的小對象。如果它們是GC'ed,它可以鎖定整個線程集嗎? – 2013-02-19 16:33:55

+4

啓用Microsoft Symbol Server以獲得更好的堆棧跟蹤。考慮到漫長的等待,這看起來像純垃圾收集。 – 2013-02-19 16:59:43

回答

0

你可以嘗試使用GCLatencyMode.LowLatency;參閱相關的問題在這裏:Prevent .NET Garbage collection for short period of time

我最近沒有運氣嘗試這個。當我在顯示的表單上緩存圖標大小的位圖圖像時,垃圾收集仍在被調用。對我來說有效的是使用螞蟻性能分析器和反射器來查找引起GC.Collect的確切調用並解決它。

+0

這裏沒什麼奇怪的。我在短時間內分配大量對象,但機器有很多內存,所以如果我能做到的話,我寧願「停下來」一會兒GC。 – 2013-02-19 21:36:50

+0

你有分配發生在循環內嗎?他們可以搬出去嗎?請參閱http://stackoverflow.com/questions/3412003/allocating-memory-inside-loop-vs-outside-loop – Kim 2013-02-20 15:07:47

+0

嗯,每個循環從數據庫中讀取自己的數據,處理數據並生成一個結果,以便進一步存儲處理。之後,生成的數據被丟棄。我沒有看到避免這種情況的方法... – 2013-02-20 21:27:25

3

如果您的StockModel.Quotation類允許,您可以創建一個池來限制創建的新對象的數量。這是他們在遊戲中有時使用的一種技術,可以防止垃圾收集器在渲染過程中停滯不前。

這裏有一個基本的池實現:

class StockQuotationPool 
    { 

     private List<StockQuotation> poolItems; 
     private volatile int itemsInPool; 

     public StockQuotationPool(int poolSize) 
     { 
      this.poolItems = new List<StockQuotation>(poolSize); 
      this.itemsInPool = poolSize; 

     } 

     public StockQuotation Create(string name, decimal value) 
     { 
      if (this.itemsInPool == 0) 
      { 
       // Block until new item ready - maybe use semaphore. 
       throw new NotImplementedException(); 
      } 

      // Items are in the pool, but no items have been created. 
      if (this.poolItems.Count == 0) 
      { 
       this.itemsInPool--; 
       return new StockQuotation(name, value); 
      } 

      // else, return one in the pool 
      this.itemsInPool--; 

      var item = this.poolItems[0]; 
      this.poolItems.Remove(item); 

      item.Name = name; 
      item.Value = value; 

      return item; 
     } 

     public void Release(StockQuotation quote) 
     { 
      if (!this.poolItems.Contains(quote) 
      { 
       this.poolItems.Add(quote); 
       this.itemsInPool++; 
      } 
     } 

    } 

這是假設StockQuotation看起來是這樣的:

class StockQuotation 
    { 
     internal StockQuotation(string name, decimal value) 
     { 
      this.Name = name; 
      this.Value = value; 
     } 


     public string Name { get; set; } 
     public decimal Value { get; set; } 
    } 

然後,而不是調用新StockQuotation()構造函數,你問池一個新的實例。該池返回一個現有的實例(如果需要,可以預先創建它們)並設置所有屬性,以使其看起來像一個新實例。您可能需要四處遊玩,直到找到足夠大的池大小,以便同時容納線程。

下面是你在線程中調用它。

// Get the pool, maybe from a singleton. 
    var pool = new StockQuotationPool(100); 


    var quote = pool.Create("test", 1.00m); 


    try 
    { 
     // Work with quote 

    } 
    finally 
    { 
     pool.Release(quote); 
    } 

最後,這個類不是線程目前是安全的。讓我知道你是否需要任何幫助。

+0

不錯的想法,我會考慮這一點。問題是,因爲這是以前沒有考慮過的,我不確定知道什麼時候可以調用版本的邏輯。 – 2013-02-26 15:35:40

+0

我已更新代碼以包含try/finally。發生異常時,您不想丟失對象。 – 2013-02-26 15:40:15

+0

關於何時調用pool.Release(),我會說基本就在它超出範圍之前,如果它在方法中。如果它存儲在對象的字段中,則可以使該對象成爲Disposable對象,並在Dispose方法中調用Release()。 **越早釋放它,越好**,游泳池的尺寸越小。 – 2013-02-26 15:44:08

相關問題