C# 值类型参数复制线程安全



假设我有一定的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混合在12345之间。

大于 32 位类型的任何基元操作在x86计算机上都不是原子操作,与 64 位和x64相同。

这并不意味着您可以在安全int上使用任何操作,例如:

从多核处理器操作int,比方说,从 2 个线程++someInt,即使它编译为incx86指令,也可能发生以下情况。

  • someInt是 9。
  • 核心 1 从其缓存中获取someInt
  • 核心 2 从缓存中获取someInt
  • 核心 1 将其具有的值增加到 10 并将其保存到缓存中。
  • Core 2 将其值增加到 10 并将其保存到缓存中。

someInt增加了两次,但它等于 10。

例如,改用Interlocked将编译为lock inc这将锁定总线并确保内核对相关缓存具有独占所有权。

复制操作对于大于int的任何内容都不是线程安全的(它是立即获取的,因此没有任何内容可以干扰其值)。

如果您采用大于 4 个字节的任何值,则可能会以 4 x 4 字节的方式获取该值,并且在这些操作之间,另一个线程可能会更改该值的一部分。

例如,加载long不是线程安全的,因为在 32 位计算机上,另一个线程可以将其写入,然后您将读取一半旧值和另一半新值。

所有这些都代表结构,如果它们占用超过 4 个字节。

最新更新