在C#7的元组之前,交换两个变量的标准方法是:
var foo = 5;
var bar = 10;
var temp = foo;
foo = bar;
bar = temp;
但现在我们可以使用
了(foo, bar) = (bar, foo);
它在一条线上,它更漂亮。但它是否是线程安全的 - 是原子上完成的交换,还是只是在多步操作之上的糖?
答案 0 :(得分:1)
“不,基本上”。
ValueTuple<...>
系列是可变值类型,这使得它很复杂。较旧的Tuple<...>
系列是不可变引用类型; “不可变”很重要,因为它意味着它不会改变单个字段 - 它正在创建一个带有所有值的新对象。 “引用类型”很重要,因为这是一个单独的引用交换, 线程安全,因为你无法获得“撕裂引用”。它在其他方面不是线程安全的:不保证订购或寄存器等。
但是ValueTuple<...>
即使这样也消失了。因为它是一种可变类型,所以很可能实现为多个ldloca
/ ld...
/ stfld
指令,所以即使值类型不大于CPU宽度,并不能保证它将全部写入单个CPU指令 - 而且几乎肯定不会。在“返回值,分配整个事物”场景中,如果足够小,可能是单个CPU指令,但它可能不会!为了使它更加复杂,另外到可变字段方法,还有也自定义构造函数 - 但它们仍将最终写入相同的内存位置(对于值)类型,目标托管引用将传递给构造函数,而不是传递 out 的构造值。
语言或运行时无法保证元组的原子性;他们只保证引用和某些原语 - 即使是 :很多很多 。
最后,还依赖目标CPU;显然,一个2-int元组不能在32位CPU上是原子的。
答案 1 :(得分:1)
您的第一种方法编译为:
.method private hidebysig static
void Method1 () cil managed
{
// Method begins at RVA 0x2068
// Code size 13 (0xd)
.maxstack 1
.locals init (
[0] int32,
[1] int32,
[2] int32
)
// (no C# code)
IL_0000: nop
// int num = 5;
IL_0001: ldc.i4.5
IL_0002: stloc.0
// int num2 = 10;
IL_0003: ldc.i4.s 10
IL_0005: stloc.1
// int num3 = num;
IL_0006: ldloc.0
IL_0007: stloc.2
// num = num2;
IL_0008: ldloc.1
IL_0009: stloc.0
// num2 = num3;
IL_000a: ldloc.2
IL_000b: stloc.1
// (no C# code)
IL_000c: ret
} // end of method Program::Method1
你的第二种方法编译为:
.method private hidebysig static
void Method2 () cil managed
{
// Method begins at RVA 0x2084
// Code size 13 (0xd)
.maxstack 2
.locals init (
[0] int32,
[1] int32,
[2] int32
)
// (no C# code)
IL_0000: nop
// int num = 5;
IL_0001: ldc.i4.5
IL_0002: stloc.0
// int num2 = 10;
IL_0003: ldc.i4.s 10
IL_0005: stloc.1
// int num3 = num2;
IL_0006: ldloc.1
// int num4 = num;
IL_0007: ldloc.0
IL_0008: stloc.2
// num = num3;
IL_0009: stloc.0
// num2 = num4;
IL_000a: ldloc.2
IL_000b: stloc.1
// (no C# code)
IL_000c: ret
} // end of method Program::Method2
正如您所看到的,他们的编译方式略有不同,但有效地采取了类似的步骤来实现相同的目标。
一个调用Load-Store-Load-Store-Load-Store,另一个调用Load-Load-Store-Store-Load-Store。
这里唯一有趣的注意事项是元组版本分配额外的内存,因为它一次在堆栈上存储两个项目,而第一种方法在任何给定时间只在堆栈中存储一个项目。“ p>
答案 2 :(得分:1)
扩展之前的答案......
不,不是线程安全的,但请记住每个线程都有自己的局部变量,除非你交换共享的东西,否则你不必担心。
如果要交换共享值,那么可以使用许多技术使其保持线程安全。你可以使用锁。您可以将多个变量放入对象并以原子方式交换对象。您可以使用.NET并发容器。这取决于你想要做的具体细节。