假设我有一定的struct
:
struct Foo{}
已在堆上分配(作为class
成员),并且我有许多线程可以读取和写入该struct
。 如果我将变量传递给某个函数:
void Bar(Foo param);
参数将是该struct
的副本。 复制操作本身线程是否安全?
永远不要假设默认情况下任何操作都是线程安全的 - 因为它不是。
例如,请考虑以下Types
:
public class A
{
public B b;
}
public struct B
{
public int a;
public int b;
public int c;
public int d;
}
和以下方法:
public static void Func(B b)
{
Console.WriteLine($"{b.a}, {b.b}, {b.c}, {b.d}");
}
如果您将具有以下Main
方法并偶尔暂停控制台一次:
A a = new A();
new Thread(() =>
{
while (true)
{
a.b.a = 5;
a.b.b = 5;
a.b.c = 5;
a.b.d = 5;
}
}).Start();
while (true)
{
a.b.a = 1;
a.b.b = 2;
a.b.c = 3;
a.b.d = 4;
Func(a.b);
}
你会看到一些structs
混合在1
、2
、3
、4
和5
之间。
大于 32 位类型的任何基元操作在x86
计算机上都不是原子操作,与 64 位和x64
相同。
这并不意味着您可以在安全int
上使用任何操作,例如:
从多核处理器操作int
,比方说,从 2 个线程++someInt
,即使它编译为inc
x86
指令,也可能发生以下情况。
someInt
是 9。- 核心 1 从其缓存中获取
someInt
。 - 核心 2 从缓存中获取
someInt
。 - 核心 1 将其具有的值增加到 10 并将其保存到缓存中。
- Core 2 将其值增加到 10 并将其保存到缓存中。
someInt
增加了两次,但它等于 10。
例如,改用Interlocked
将编译为lock inc
这将锁定总线并确保内核对相关缓存具有独占所有权。
复制操作对于大于int
的任何内容都不是线程安全的(它是立即获取的,因此没有任何内容可以干扰其值)。
如果您采用大于 4 个字节的任何值,则可能会以 4 x 4 字节的方式获取该值,并且在这些操作之间,另一个线程可能会更改该值的一部分。
例如,加载long
不是线程安全的,因为在 32 位计算机上,另一个线程可以将其写入,然后您将读取一半旧值和另一半新值。
所有这些都代表结构,如果它们占用超过 4 个字节。