问题
Before C# 7's tuples, the standard way to swap two variables was something like:
var foo = 5;
var bar = 10;
var temp = foo;
foo = bar;
bar = temp;
But now we can use
(foo, bar) = (bar, foo);
It's on one line and it's prettier. But is it thread safe - is the swap done atomically, or is this just sugar over top of a multi-step operation?
回答1:
"No, basically".
The ValueTuple<...>
family are mutable value types, which makes it complex. The older Tuple<...>
family were immutable reference types; the "immutable" matters because it means it isn't changing individual fields - it is creating a new object with all the values. The "reference-type" matters because this is then a single reference exchange which is thread-safe in as much as you can't get a "torn reference". It isn't thread-safe in other ways: there's no guarantees about ordering or registers, etc.
But with ValueTuple<...>
even that is gone. Because it is a mutable type, this is most likely implemented as multiple ldloca
/ld...
/stfld
instructions, so even if the value type is not larger than the CPU width, there's no guarantee that it will all be written in a single CPU instruction - and it almost certainly won't be. In the "return a value, assign the entire thing" scenario it might be a single CPU instruction if small enough, but it might not! To make it even more complex, in addition to the mutable field approach, there are also custom constructors - but they will still ultimately be writing over the same memory location (for value types, the destination managed reference is passed into the constructor, rather than the constructed value being passed out).
There is no guarantee whatsoever made by the language or runtime about tuple atomicity; they only make guarantees about references and certain primitives -additionally, even if it was: thread-safety is a lot more than just atomicity.
Finally, it would also depend don the target CPU; obviously a 2-int tuple cannot be atomic on a 32-bit CPU.
回答2:
Your first method compiles as:
.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
And your second method compiles as:
.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
As you can see they both compile down slightly differently but effectively take similar steps to accomplish the same goal.
One calls Load-Store-Load-Store-Load-Store, where the other calls Load-Load-Store-Store-Load-Store.
The only interesting note here is that the tuple version allocates additional memory as it stores two items on the stack at one time, versus the first method which stores only a max of one item on the stack at any given time.
回答3:
Expanding on the previous answers...
No, not thread safe, BUT remember that each thread has its own local variables and unless you're swapping shared stuff you don't have to worry.
If you are swapping shared values then there are a number of techniques you can use to make it thread safe. You can use locks. You can put multiple variables into an object and swap the object atomically. You can use a .NET concurrent container. It depends upon the specifics of what you want to do.
来源:https://stackoverflow.com/questions/48550397/is-a-c-sharp-7-tuple-based-variable-swap-thread-safe