C# 结构体是线程安全的吗?
例如,如果有:
struct Data
{
int _number;
public int Number { get { return _number; } set { _number = value; } }
public Data(int number) { _number = number; }
}
另一种类型:
class DadData
{
public Data TheData { get; set; }
}
名为 TheData 的属性是线程安全的吗?
嗯 - 最佳实践是结构应该始终是不可变的(除了一些非常特定的场景,甚至存在风险)。并且不可变数据始终是线程安全的。因此,如果您遵循最佳实践并做到了这一点:
struct Data
{
readonly int _number;
public int Number { get { return _number; } }
public Data(int number) { _number = number; }
}
那么是的;那是线程安全的。在所有其他情况下,答案都是“可能不会”。
另请注意,原子性规则适用,因此即使对
DadData.TheData
的单个读取或更新也不能被认为是线程安全的,即使 with 是不可变的结构。您可以(特别是对于超大结构)让一个线程读取该结构,而另一个线程重写它;如果没有同步,不好的事情将会发生(最终)。
不,.NET 中的结构本质上不是线程安全的。
但是,结构中的按值复制语义与此对话有很大的相关性。
如果您传递结构并以某种方式将它们分配给变量或按值传递参数(无 ref 或 out 关键字),则正在使用 copy。
当然,这意味着对副本所做的任何更改都不会反映在原始结构中,但在传递它们时需要注意。
如果您以不涉及按值复制语义的方式直接访问结构(例如,访问作为结构类型的静态字段,并且正如 Marc Gravel 在他的回答中指出的那样),有许多其他方式)跨多个线程,那么您必须考虑实例的线程安全性。
struct
并不比普通字段或变量更线程安全。如果您至少有一个线程修改它,并且至少还有一个线程同时以任何方式接触它,您可能会出现意外/未定义的行为。此外,可变结构也是代码异味。是否有某些特殊原因需要它成为
struct
而不是
class
?您需要此数据的值类型语义吗?
保存结构的可变存储位置除了彻底替换之外不提供任何突变方式,不提供任何线程安全性,除非结构保存单个 32 位整数或单个对象引用,尝试读取此类(单项)结构存储位置在写入的同时保证读取完全旧的数据或全新的数据。请注意,不可能将任何 Interlocked 方法与不可变结构一起使用 - 即使是仅包含单个整数或对象引用的结构。
namespace StructThreadSafe
{
class Program
{
struct BankBalance
{
public decimal Balance { get; set; }
}
static void Main(string[] args)
{
BankBalance bankBalance = new BankBalance();
bankBalance.Balance = 100;
List<Task> allTasks = new List<Task>();
for (int q = 0; q < 10; q++)
{
Task producer = new Task(() =>
{
for (int i = 0; i < 1000; i++)
{
if (bankBalance.Balance < 0)
{
if (Debugger.IsAttached)
{
Debugger.Break();
}
}
bankBalance.Balance += 5;
Console.WriteLine("++Current Balance: " + bankBalance.Balance);
System.Threading.Thread.Sleep(100);
}
});
allTasks.Add(producer);
}
for (int w = 0; w < 10; w++)
{
Task consumer = new Task(() =>
{
for (int i = 0; i < 1000; i++)
{
if (bankBalance.Balance < 0)
{
if (Debugger.IsAttached)
{
Debugger.Break();
}
}
if (bankBalance.Balance > 15)
{
bankBalance.Balance -= 15;
Console.WriteLine("--Current Balance: " + bankBalance.Balance);
}
else
{
Console.WriteLine("**Current Balance below minimum: " + bankBalance.Balance);
}
System.Threading.Thread.Sleep(100);
}
});
allTasks.Add(consumer);
}
allTasks.ForEach(p => p.Start());
Task.WaitAll(allTasks.ToArray());
}
}
}