如何以线程安全的方式递增(增加值)十进制?

时间:2013-10-22 12:02:10

标签: c# thread-safety

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

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

5 个答案:

答案 0 :(得分:13)

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

System.Decimal之类的结构类型从不是原子的,它也不适合本机cpu字大小。这就是为什么Interlocked也没有过载的原因。

答案 1 :(得分:3)

没有。 decimal的内部表示过于复杂,无法在CPU级别使用原子指令进行修改(这是Interlocked在大多数情况下执行的操作,以及您感兴趣的内容。“ / p>

当CPU无法自动处理某些数量时,手动锁定是唯一的选择。你可以选择同步原语(例如lock vs互斥),但就是这样。

答案 2 :(得分:0)

您仍然可以使用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,335Int64.MaxValue9,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而且我确信在Parallel.For循环中使用Interlocked这种方式更快,然后使用lock或互斥。

答案 3 :(得分:0)

如果您不介意将总数保留为对象包裹的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包装在一个对象中,这会带来其自身的性能影响。根据具体情况,使用锁可能会更便宜。

答案 4 :(得分:0)

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

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

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

x=y;//atomic