using System;
using System.Threading;
using System.Threading.Tasks;
namespace _1._41_Compare_and_Exchange_as_a_nonAtomic_operation
{
public class Program
{
static int value = 1;
public static void Main()
{
Task t1 = Task.Run(() =>
{
if (value == 1)
{
Thread.Sleep(1000);
value = 2;
}
});
Task t2 = Task.Run(() =>
{
value = 3;
});
Task.WaitAll(t1, t2);
Console.WriteLine(value); //Displays 2
}
}
}
我正在尝试将上述非原子操作转换为原子操作,使用:
Interlocked.CompareExhange(ref value, newValue, compareTo);
我写的是
Interlocked.CompareExhange(ref value, value, value); //This doesn't look right!
和
Interlocked.CompareExhange(ref value, t2, t1); //will not compile
问题
Interlocked.CompareExchange
? 答案 0 :(得分:2)
有或没有Interlocked.CompareExchange值输出为2?为什么呢?
真正的答案是:巧合。您的代码是非确定性的,其行为取决于OS线程调度程序。
但是...... t1
在t2
之前开始执行实际上并不奇怪。在这种情况下,if (value == 1)
检查是在t2
有机会执行value = 3;
之前进行的。
总而言之,最可能的时间表是:
t1
:针对value
1
t1
:t1
入睡一秒t2
:value
设置为3 t1
:稍后醒来t1
:value
设置为2 但正如我上面所说,这就是实践中发生的事情,但是游览代码仍然不确定,因为t2
原则上可以它在t1
之前执行。
引用t1,t2的正确方法是什么?
如果我正确理解你的问题,你的方式似乎是正确的。
为什么我不能直接引用任务的输出? (值,t2,t1)
您启动void
个任务,这些任务首先不会拥有输出。它们由Task
类型表示。
您可以启动一个返回如下结果的任务:
var t = Task.Run(() => {
// do anything
return 42;
});
在这种情况下,t
将为Task<int>
类型,并且您可以在任务结束时访问其Result
属性(如果您尝试在任务之前访问i)完成后,它将一直阻止,直到任务结束。)
是否需要进行某种转换?
我不确定我在这里理解你的问题。
据我所知,值应更新为2(值= 1)然后更新为3而不是当前输出(值= 1,更新为3然后将其更新为2)一旦使用Interlocked.CompareExchange?
差不多,是的。 Interlocked.CompareExchange
是 atomic 。它将执行比较和在硬件级别一步设置值:
Interlocked.CompareExchange(ref value, 2, 1);
这是 atomic 等价于:
if (value == 1)
value = 2;
如果你这样做,最终的值总是为3
。因为两种不同的场景是可能的(这很简单,因为现在两个任务都包含一个原子语句):
t1
在t2
之前执行:
t1
:针对value
检查1
,这是真的,因此value
设置为2
t2
:value
设置为3 t2
在t1
之前执行:
t2
:value
设置为3 t1
:针对value
检查1
,这是假的,因此value
保持不变正如您所看到的,在这两种情况下,您最终都会得到3
。