我为什么要在这里锁?

时间:2013-02-19 16:09:57

标签: c# .net multithreading performance locking

请参阅以下并发性能分析,该分析表示并行foreach所做的工作:

enter image description here

在循环内部,每个线程从数据库中读取数据并对其进行处理。线程之间没有锁,因为每个处理不同的数据。

由于未知原因,看起来foreach的所有线程都有定期锁定(请参阅黑色垂直矩形)。如果您看到所选的锁定段(深红色段),您将看到堆栈显示在StockModel.Quotation构造函数处锁定的线程。那里的代码只构造了两个空列表!

我在某处读过这可能是由GC造成的,因此我将垃圾收集更改为在服务器模式下运行:

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

我得到了一个小的改进(大约10% - 快15%),但我仍然到处都有垂直锁。

我还在所有数据库查询中添加了WITH(NOLOCK),因为我只读数据而没有任何区别。

这里有什么暗示?

进行分析的计算机有8个核心。

编辑:启用Microsoft Symbol服务器后,所有线程都会在wait_gor_gc_done或WaitUntilGCComplete等调用中被阻止。我认为启用GCServer我为每个线程都有一个GC,所以我会避免“垂直”锁定,但似乎并非如此。我错了吗?

第二个问题:由于机器没有内存压力(使用了8个演出中的5个)有一种方法可以延迟GC执行或暂停它直到并行foreach结束(或将其配置为较少发射) ?

2 个答案:

答案 0 :(得分: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);
    }

最后,此类目前不是线程安全的。如果您需要任何帮助,请告诉我。

答案 1 :(得分:0)

您可以尝试使用GCLatencyMode.LowLatency;在此处查看相关问题:Prevent .NET Garbage collection for short period of time

我最近尝试了这个没有运气。在我正在显示的表单上缓存Icon大小的位图图像时,仍然会调用垃圾收集。对我有用的是使用Ants性能分析器和Reflector来查找导致GC.Collect并解决它的确切调用。