比较和交换作为非原子操作

时间:2015-01-06 14:27:03

标签: c#

我有一个book about programming in C#.有一个例子。让我复制并粘贴上下文。

...介绍 Interlocked 的用法(此处省略)。

  

您还可以使用 CompareExchange 方法。该方法首先检查预期值是否存在;如果是,则用其他值替换它。

     

清单1-41显示了在非原子操作中比较和交换值时可能出现的问题。

     

列表1-41比较和交换作为非原子操作

class Program
{
    static int value = 1;
    static void Main(string[] args)
    {
        Task t1 = Task.Run(() =>
            {
                if(value==1)
                {
                    // Removing the following line will change the output
                    Thread.Sleep(1000);
                    value = 2;
                }
            });
        Task t2 = Task.Run(() =>
        {
            value = 3;
        });
        Task.WaitAll(t1, t2); // Displays 2
        Console.WriteLine(value);
    }
}
  

任务t1 开始运行,并发现等于 1 。同时, t2 将值更改为 3 ,然后 t1 将其更改回 2 。为避免这种情况,您可以使用以下 Interlocked 语句:

Interlocked.CompareExchange(ref value, newValue, compareTo);
  

这确保比较值并将其交换为新值是原子操作。这样,没有其他线程可以在比较和交换它之间改变值。

这本书没有提供进一步的细节,现在就结束本节。

我的问题是如何将Interlocked.CompareExchange(ref value, newValue, compareTo);应用于代码?

4 个答案:

答案 0 :(得分:4)

嗯,我猜他们的意思是替换非原子块:

if(value==1)
{
    // Removing the following line will change the output
    Thread.Sleep(1000);
    value = 2;
}

使用

Interlocked.CompareExchange(ref value, 2, 1);

编辑,Interlocked.CompareExchange解决了什么问题?

两个任务t1和t2都可能同时改变value

此代码序列是多线程代码中常见的反模式 - ABA problem

if(someUnsynchronizedCondition)
{
    ... Other random code here
    doSomeActionDependentOnTheCheckedCondition();
}

问题在于,(if (value==1))检查的条件在采取相关操作时不再是真实的(在您的情况下为value = 2;),因为其他线程可以在此线程从if检查移动到相关操作所需的同一时间内更改变量(显然,"其他随机代码"需要的时间越多,就像2第二次睡觉,风险越大)。但总是有风险,即使中间没有额外的代码(即使完全删除Thread.Sleep,原始代码仍然倾向于ABA。)

在本书的示例中,操作是改变条件中使用的相同变量,因此使用Interlocked.CompareExchange执行此操作的便捷方式 - 对于Intel处理器,这转换为单个{ {1}} atomic instruction

在更一般的意义上(即,依赖操作不仅仅是更改已检查的变量),您需要使用其他同步方式,例如lock cmpxchg [reg1], reg2或{ {3}},特别是如果对读者的偏见比作家更偏向。

回复:最终结果

是的,代码现在总是显示3,只是因为如果第一个lock t1首先执行,它会将值更改为2,这将被第二个Task t2否决,将其设置为3,但如果第二个任务首先运行(设置为3),则t1不会更新该值,因为它是3而不是1。

答案 1 :(得分:3)

Interlocked.CompareExchange(ref value, newValue, compareTo);基本上是这样做的:

if(value==compareTo)
{
    value = newValue;
}

但是在 atomic 中,换句话说,是不可中断的时尚。这可以防止您在检查值的示例代码中遇到的问题,但在为其分配新值之前,值会更改。这在具有共享变量的多线程代码中非常重要。

您可能会看到其他类似的技术。例如,使用对象lock

lock(someObject) 
{
    if(value==compareTo)
    {
        value = newValue;
    }
}

但这只有在其他所有尝试更改同一对象上的value锁定时才有效。

答案 2 :(得分:2)

它取代了单独的比较和交换指令。在t1的这个块中:

if(value==1) //Compare
{
    Thread.Sleep(1000);
    value = 2; //Exchange
}

希望你会看到当t1睡着时,t2可以切入并将值设置为3,因为你的比较和交换是分开的 - 即不是原子的。

如果用以下内容替换整个块:

Interlocked.CompareExchange(ref value, 2, 1);

然后运行时保证比较和交换一起完成,并且在这些操作的中间没有其他线程可以切断。

这不是一个很好的例子,因为你也必然会丢失Thread.sleep,这使得跟踪恕我直言不太直观。

答案 3 :(得分:0)

class Program
{
    static int value = 1;
    static void Main(string[] args)
    {
        Task t1 = Task.Run(() =>
        {
            Interlocked.CompareExchange(ref value, 2, 1);
        });
        Task t2 = Task.Run(() =>
        {
            value = 3;
        });
        Task.WaitAll(t1, t2);
        Console.WriteLine(value);
    }
}