【问题标题】:Should I use a lock object while Cloning a ConcurrentDictionary in C#?在 C# 中克隆 ConcurrentDictionary 时是否应该使用锁定对象?
【发布时间】:2019-09-06 06:00:42
【问题描述】:

我有一个代码库,其中多个线程正在写入ConcurrentDictionary,并且每隔 60 秒另一个线程运行并克隆主 CD,将其清除,然后继续在克隆的 CD 上工作。我想知道如果我不使用lockCloning and Clearing 主CD,我会错过一些数据吗?演示问题的代码如下:


class Program
    {
        static object lock_obj = new object();

        static async Task Main(string[] args)
        {
            ConcurrentDictionary<string, ThreadSafeLong> cd = new ConcurrentDictionary<string, ThreadSafeLong>();


            Func<Task> addData = () =>
            {
                return Task.Run(async () =>
               {
                   var counter = 1;

                   while (true)
                   {
                       lock (lock_obj)
                       {
                           for (int i = 0; i < 100_000; i++)
                           {
                               cd.TryAdd($"{counter}:{i}", new ThreadSafeLong(i));
                               //WriteLine(i);
                           }
                           WriteLine($"Round {counter}");
                       }
                       counter++;
                       await Task.Delay(1_000);
                   }
               });
            };

            Func<Task> writeData = () =>
            {
                return Task.Run(async () =>
              {
                  while (true)
                  {
                      var sw = Stopwatch.StartNew();

                      lock (lock_obj) // to clone the data, and prevent any other data to be added while clone
                      {
                          var cloned = new ConcurrentDictionary<string, ThreadSafeLong>(cd);
                          cd.Clear();
                          WriteLine($"Cloned Count: {cloned.Count}");
                      }

                      sw.Stop();
                      WriteLine($"Elapsed Time: {sw.ElapsedMilliseconds}");

                      await Task.Delay(6_000);
                  }
              });
            };

            await Task.WhenAll(addData(), writeData());

        }
    }

PS:可能与here的问题有关

【问题讨论】:

  • 所以你有多个生产者和一个消费者,但是消费者分批接收它的工作。专门使用字典来保存排队的请求批次有什么好处/要求?
  • 我为每个键保留一个聚合值,我不希望每次点击都是单独的输入,所以我使用了字典,并且每次都增加值。
  • 无竞争锁很便宜。只要你不持有锁太久,就可以了。努力减少您需要持有锁的时间,而不是试图完全移除它。
  • 你为什么使用ConcurrentDictionary?由于您在每次读写之前都会锁定,因此一个简单的Dictionary 就足够了。
  • @TheodorZoulias,你是对的,最初的问题是如何克隆一个ConcurrentDictionary,但是在我们完成了我的示例之后,就不再需要了。

标签: c# collections thread-safety clone concurrentdictionary


【解决方案1】:

在这些情况下,我会用新字典替换字典,而不是调用 clear:

lock (lock_obj)
{
    var cloned = cd;
    cd = new ConcurrentDictionary<string, ThreadSafeLong>();
}

在这种情况下,其他线程已完成对旧线程的写入或已经在使用新线程。

【讨论】:

  • 没有任何锁定?
  • 我将示例更改为您所说的内容,但有时我们似乎遗漏了一项!!!
  • @SaeedGanji 你需要锁定。否则写入线程可能会在一段时间内写入克隆副本,即使在您切换到新副本之后也是如此。
  • @SaeedGanji Oliver 的方法比将一本字典中的每一项都复制到另一本字典要便宜。但是,它不会改变任何锁定需求 - 风险是编写者会继续写入克隆,即使在您创建了新的空 ConcurrentDictionary 之后也是如此。如果这是您需要避免的事情,则需要锁定以确保在进行克隆之前所有编写器都已停止写入。您可以使用 ReaderWriterLockSlim 来执行此操作 - 尝试写入字典的线程获得读锁,而线程克隆获得写锁
  • @SaeedGanji 问题是如果他们在您处理完它之后 写信给克隆!当您在没有锁的情况下写入字段时,只有执行写入的线程会立即通知。其他线程可能需要一段时间才能注意到。锁添加了一个内存屏障,它可以避免这种情况,只要读取器和写入器都获取锁。在写入 CD 时,写入线程也需要锁定,以避免在写入线程读取 CD 字段之后,但在写入 CD 之前创建克隆。
猜你喜欢
  • 1970-01-01
  • 2011-02-04
  • 2010-09-08
  • 2014-08-06
  • 1970-01-01
  • 2017-04-24
  • 2010-09-14
  • 1970-01-01
相关资源
最近更新 更多