2013-02-19 95 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