14

我正在将对象添加/更新到并发字典中并定期(每分钟)刷新字典,因此我的代码如下所示:

    private static ConcurrentDictionary<string, Metric> _metrics = new ConcurrentDictionary<string, Metric>();

    public static void IncrementCountMetricBy(string name, int count)
    {           
        _metrics.AddOrUpdate(....
    }

    public static Metric[] Flush()
    {
        var flushedMetrics = _metrics;
        _metrics = new ConcurrentDictionary<string, Metric>();
        return flushedMetrics.Values.ToArray();
    }

现在我不确定这段代码是否有可能丢失一些对象/更新

4

2 回答 2

15

是的,您可能在那里丢失一些数据:

  1. 递增线程可以读取字段并获取旧字典,_metrics然后被中断
  2. 然后刷新线程_metrics用新字典替换该字段
  3. 刷新线程比调用Values.ToArray()
  4. 然后递增线程调用AddOrUpdate一个不再被任何东西查看的字典。(它在步骤 1 中获取的那个。)

换句话说,想象你的IncrementMetricCountBy方法实际上是:

public static void IncrementCountMetricBy(string name, int count)
{
    var tmp = _metrics;
    Thread.Sleep(1000);           
    tmp.AddOrUpdate(...);
}

如果你能明白为什么这不安全,那么同样的论点也适用于你当前的代码。

据我所知,这里没有什么特别简单的事情可以做ConcurrentDictionary。一种选择是拍摄所有密钥的快照,然后将它们全部删除:

var keys = _metrics.Keys.ToList();
var values = new List<Metric>();
foreach (var key in keys)
{
    Metric metric;
    if (_metrics.TryRemove(key, out metric))
    {
        values.Add(metric);
    }
}
return values;

返回时字典可能不为,但不应丢失任何数据。(您可能会在方法开始后更新指标,并且在删除密钥后发生的任何更新最终都会重新添加它,但这应该没问题。)

于 2013-06-19T15:21:33.263 回答
5

这是。考虑以下情况:

  1. 线程一调用AddOrUpdate,但在调用开始后立即停止执行,在该方法中采取任何操作(包括取出任何锁)之前。

  2. 线程两个复制字典的所有值。

  3. 线程一返回以完成添加项目。

然后该项目将丢失。

于 2013-06-19T15:22:00.803 回答