【问题标题】:Are C# structs thread safe?C#结构线程安全吗?
【发布时间】:2011-01-22 02:52:59
【问题描述】:

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 的属性是线程安全的吗?

【问题讨论】:

标签: c# concurrency struct thread-safety parallel-processing


【解决方案1】:

嗯 - 最佳实践是结构应该始终(除了在一些非常特殊的情况下,甚至在风险中)是不可变的。不可变数据始终是线程安全的。因此,如果您遵循最佳实践并做到了这一点:

struct Data
{
    readonly int _number;
    public int Number { get { return _number; } }

    public Data(int number) { _number = number; }
}

那么是的;那是线程安全的。在所有其他情况下,答案都是“可能不会”。

还要注意原子性规则适用,因此即使是对DadData.TheData 的单次读取或更新也不能被认为是线程安全的,即使使用 是不可变结构。你可以(特别是对于超大的结构)让一个线程读取结构,而另一个线程重写它;没有同步就会发生坏事(最终)。

【讨论】:

    【解决方案2】:

    不,.NET 中的结构本质上不是线程安全的。

    但是,结构的按值复制语义与这种对话有很大的相关性。

    如果您要传递您的结构并以某种方式将它们分配给变量或按值传递的参数(没有 ref 或 out 关键字),那么将使用 copy

    当然,这意味着对副本所做的任何更改都不会反映在原始结构中,但在传递它们时需要注意。

    如果您以不涉及按值复制语义的方式直接访问结构(例如访问作为结构类型的静态字段,如Marc Gravel points out in his answer,还有许多其他方式)跨多个线程,那么您必须考虑实例的线程安全性。

    【讨论】:

    • 更糟的是,它还取决于你的代码是否与 fieldvariable 进行对话。 i> 或属性。幸运的是,在这种情况下,自动实现的属性 (TheData) 删除了其中的大部分。所以我只是为了完整性而提到它;-p
    • 谢谢 casperOne!谢谢你,马克!正如 casperOne 所说,我认为“结构与此对话密切相关的按值复制语义”;但没有看到任何关于参考的说明。这段代码不是我的实际代码,而是一个表示。我正在编写一些多线程应用程序;一些常见的并行编程工具是 C#;因为有些模式经常返回;)(当我提出有价值的东西时,我肯定会在这里寻找批评)。
    【解决方案3】:

    struct 并不比普通字段或变量更具有线程安全性。如果您至少有一个线程在修改它,并且至少有一个线程同时以任何方式接触它,您最终可能会出现意外/未定义的行为。

    此外,可变结构是代码异味。是否有某些特殊原因需要它成为struct 而不是class?您需要此数据的值类型语义吗?

    【讨论】:

      【解决方案4】:

      不同线程对可变结构的不同成员的直接读写不会相互干扰。不同线程通过互锁方法对同一成员的访问将根据这些方法的语义进行。这些事实可能允许可变结构允许线程安全的行为。

      保存结构的可变存储位置除了完全替换之外不提供任何变异方式,不提供任何线程安全,除非在结构保存单个 32 位整数或单个对象引用的情况下,尝试读取这样的(单项)结构存储位置在写入的同时保证读取完全旧数据或完全新数据。请注意,不能将任何 Interlocked 方法与不可变结构一起使用——即使是仅包含单个整数或对象引用的结构。

      【讨论】:

        【解决方案5】:

        不,他们不是。 我创建了非常简单的应用程序来查看 10/10 生产者/消费者线程是否正在访问相同的结构变量。最终你会看到 Debugger.Break();会被击中。银行余额不得低于 0。

        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());
        
                }
            }
        }
        

        【讨论】:

          【解决方案6】:

          没有。为什么它是线程安全的?这只是数据。它不会通过魔法变得线程安全。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-12-31
            • 1970-01-01
            • 1970-01-01
            • 2019-09-20
            • 2021-01-15
            • 1970-01-01
            • 2010-09-05
            相关资源
            最近更新 更多