11

我有一个decimal同时从多个线程访问的变量。Interlocked类函数根本不支持小数,所以我剩下的唯一方法是使用lock(){}. 这似乎是一种矫枉过正。

还有其他方法可以以decimal线程安全的方式向变量添加值吗?

4

5 回答 5

18

使用锁并不过分。这是必需的。

像 System.Decimal 这样的结构类型永远不是原子的,它也不适合原生 cpu 字长。这就是为什么 Interlocked 也没有过载的原因。

于 2013-10-22T12:06:29.533 回答
4

不可以。 的内部表示decimal太复杂,无法在 CPU 级别使用原子指令进行修改(这是Interlocked大多数时间所做的,也是您感兴趣的)。

当 CPU 无法自动处理某些数量时,手动锁定是唯一的选择。您可以选择同步原语(例如lock与互斥锁),仅此而已。

于 2013-10-22T12:08:32.343 回答
3

您仍然可以使用InterLocked,但是您必须将小数转换为Int64. 通过转换,您必须决定要保留多少小数位以确保精度。因此,例如,您想保留 4 个小数位,您可以执行以下操作:

        //Declare up front accessible from all threads
        Int64 totalAmount = 0;

        //Inside the thread you do this
        var amount = (Int64)(decimalAmount * 10000); //10.000 is to preserve 4 decimal places
        Interlocked.Add(ref totalAmount, amount);

        //After all threads have finished, go back to decimal type.
        var totalDecimalAmount = totalAmount / 10000;

请注意,您将失去精度,具体取决于您要保留多少小数位。并且Decimal.MaxValue79,228,162,514,264,337,593,543,950,335而是。Int64.MaxValue_ 9,223,372,036,854,775,807所以非常大的数字不适合。保留 4 位小数,Int64 溢出之前的最大数字是9,223,372,036,854,775,807 / 10000 = 922,337,203,685,477

我以这种方式使用它,因为这里的数字永远不会超过 1,000,000,000,而且我确信Interlocked在 Parallel.For 循环中使用这种方式比使用lock或互斥锁更快。

于 2016-12-28T08:48:34.707 回答
1

如果您不介意将总数保留为 object-wrapped decimal,则可以使用以下方法:

private static object myTotal = 0M;
static void InterlockedAddTotal(decimal val) {
    object next;
    object current;
    do {
        current = myTotal;
        next = val + (decimal)current;
    } while (Interlocked.CompareExchange(ref myTotal, next, current) != current);
}

尽管这种方法不使用锁,但它包装decimal在一个对象中,该对象具有其自身的性能影响。根据情况,使用锁可能更便宜。

于 2017-12-04T19:52:24.890 回答
1

使分配原子化的一种方法是创建一个内部具有十进制值的类并将引用分配给该类。类引用的分配是原子的,因为它们是 64 (32) 位

class A
{
    decimal Value{get;set;}
}

var x=new A(){Value=10};
var y=new A(){Value=20};

x=y;//atomic
于 2019-03-31T20:41:04.093 回答