c# - 使计算线程安全的标准方法是什么?

标签 c# .net multithreading thread-safety

最初看似简单解决方案的问题已被证明是一个非常有趣的挑战。

我有一个类维护一个内部固定大小、线程安全的集合(通过对所有插入和删除操作使用 lock)并通过其属性提供各种统计值。

一个例子:

public double StandardDeviation {
    get {
        return Math.Sqrt((Sum2 - ((Sum * Sum) / Count)) / Count);
    }
}

现在,我已经彻底测试了这个计算,通过集合运行 10,000 个值并检查每次更新的标准偏差。它工作正常...在单线程场景中。

然而,在我们的开发和生产环境的多线程上下文中出现了一个问题。似乎这个数字以某种方式有时返回NaN,然后迅速变回实数。当然,这一定是由于传递给 Math.Sqrt 的负值所致。我只能想象,当计算进行到一半时,计算中使用的值之一由单独的线程更新时会发生这种情况。

我可以先缓存值:

int C = this.Count;
double S = this.Sum;
double S2 = this.Sum2;

return Math.Sqrt((S2 - (S * S) / C) / C);

但是 Sum2 可能仍会更新,例如,在设置 S = this.Sum 之后,再次影响计算。

我可以在代码中更新这些值的所有点周围放置一个:

protected void ItemAdded(double item) {
    // ...

    lock (this.CalculationLock) {
        this.Sum += item;
        this.Sum2 += (item * item);
    }
}

然后,如果我在计算 StandardDeviation锁定同一对象,我认为最终会解决问题。它没有。该值仍然以 NaN 的形式出现,但并不频繁。

坦率地说,即使上述解决方案已经 奏效,它也非常困惑并且对我来说似乎不太好管理。 是否有标准和/或更直接的方法来实现计算值的线程安全,例如这样?


编辑:原来这里有一个问题示例,起初似乎只有一个可能的解释,但毕竟问题完全出在其他问题上。

我一直一丝不苟地以各种可能的方式实现线程安全,而尽可能不牺牲巨大的性能——锁定对共享值的读取和写入(例如,SumCount),在本地缓存值,并使用相同的锁对象来修改集合和更新共享值……老实说,这一切看起来都有些矫枉过正。

没有任何效果;那个邪恶的 NaN 不断弹出。所以我决定在 StandardDeviation 返回 NaN 时将集合中的所有值打印到控制台...

我立即注意到,当集合中的所有值都相同时,似乎总是会发生这种情况

这是官方的:我被浮点运算烧坏了。 (所有的值都相同,所以 StandardDeviation 中的被除数——即被取平方根的数——被计算为某个极小的负数。)

最佳答案

I could put a lock around all points in the code where these values are updated:

protected void ItemAdded(double item) {
    // ...

    lock (this.CalculationLock) {
        this.Sum += item;
        this.Sum2 += (item * item);
    }
}

Then if I lock on this same object when calculating StandardDeviation, I thought that would, finally, fix the problem. It didn't. The value is still coming in as NaN on a fleeting, infrequent basis.

正是您应该为正确性做的事情。如果这对您不起作用,我建议您要么错过了更新场景 - 或者您有其他问题(例如 Sum 或 Sum2 偶尔为 NaN 或由于某些 而出现意外值其他竞争条件)。

关于c# - 使计算线程安全的标准方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1544142/

相关文章:

multithreading - 从单个应用程序中的多个线程调用 dll 函数是否安全?

C#用多个逗号替换字符串中的两个特定逗号

C# Regex 指定允许的开始和结束条件

c# - 文本框中显示意外字符

c# - 带有 String keySelector 的 OrderBy

java - 当 wait() 不可用时节省 CPU

c# - DataContractJsonSerializer 没有看到 DataMemberAttribute

c# - NUnit : Effort. Exceptions.EffortException : The Effort library failed to register its provider automatically, 所以需要手动注册

c# - 互连流

java - 在多任务应用程序中管理 GUI 和 EDT