在这种情况下,可变变量线程安全吗?

时间:2018-10-27 09:26:43

标签: c# multithreading immutability

考虑下面的代码示例,变量i是不可变的int类型。递增i后,新值将存储在同一存储位置。因此,如果正在从该位置读取多个线程,则在进行写入时,其中一些线程可能会损坏数据。那么这个不可变类型线程如何安全?有内部的CLR逻辑可以解决这个问题吗?

public class Test
{
    int i = 10;

    public unsafe int Run()
    {
        fixed (int* ip = &i)
        {
            Console.WriteLine($"address of i before updation: {((IntPtr)ip).ToString()}");
        }

        i = i + 1;

        fixed (int* ipNew = &i)
        {
            Console.WriteLine($"address of i after updation: {((IntPtr)ipNew).ToString()}");
        }

        return i;
    }
}

更新: 我根据之前的评论(基于注释)更新了代码。现在,如果类Test由客户端启动一次并且Run方法由多线程调用,该怎么办? i被认为是线程安全的吗?

1 个答案:

答案 0 :(得分:1)

要回答您的更新,没有代码不是线程安全的。您正在读取i,递增值,然后写入i。这三个步骤不是逻辑单元,其他线程可以在读写步骤之间与i交互。

例如,您有三个线程,即A B和C。某些事情使B的运行速度比其他线程慢。

A: Read i to thread local memory location A the value 10
B: Read i to thread local memory location B the value 10
A: Add 1 to thread local memory location A
A: Write 11 to i from thread local memory location A
B: Add 1 to thread local memory location B
C: Read i to thread local memory location C the value 11
C: Add 1 to thread local memory location C
C: Write 12 to i from thread local memory location C
B: Write 11 to i from thread local memory location B

由于这3个操作不能在3个线程之间“原子”地完成B所必须执行的3个步骤之间的工作,所以这将导致您得出错误的结束值。

处理此问题的正常方法是锁定3个操作,因此只有一个线程可以一次执行该操作,

lock(someObject)
{
   i = i + 1;
}

使用使操作原子化的工具

Interlocked.Increment(ref i);

或检查i的值在读取的开始与您要执行的写入之间是否未更改,并且如果确实更改了,请重试该操作。

int iOriginal, iNew;
do 
{
    iOriginal = i;
    iNew = iOriginal + 1;
} while(iOriginal != Interlocked.CompareExchange(ref i, iNew, iOriginal)

人们说不可变值是线程安全的原因是,它们是指将引用的副本传递给另一个函数,一旦创建了该引用的副本,您就不必担心另一个线程会更改该值。对象,而您正在使用它。但是,如果不复制参考(例如在示例中使用了函数范围之外的共享变量),则会遇到线程间使用的参考不可变性的问题。 / p>

简单来说,值10是不可变的,名称为i的变量不是。如果跨线程共享变量(我不是在谈论变量的对象引用/值,而是变量本身),那么您正在使用可变对象。