【问题标题】:C# Multithreading conflicts on shared variables共享变量上的 C# 多线程冲突
【发布时间】:2018-11-20 19:22:40
【问题描述】:

我的问题如下: 我在自己的线程 1 中运行了一个 GUI,设置了一些配置设置。

配置本身将被不同类中的另一个线程 2 使用(仅读取访问),但是当我在我的 GUI 中应用时,它应该尽快更新。

配置包含 bool 和 Dictionaries 等变量。

什么是解决这个问题的安全方法?

到目前为止我的想法: 在某个静态实例变量中包含当前配置的类:

public class Config
{
   static Config Instance;
   int a;
   Dictionary<int, int> b;
   ...
   public void update(Config newConfig) {}
}

因此线程 2 中的所有类始终可以访问最新设置。

当我在 GUI 中按下“应用”按钮来应用最新设置时,会调用更新函数。

但是线程安全呢?如何仅在线程2未读取时设置变量并在设置变量时让线程2等待?我对 c# 中的多线程没有任何经验,我知道它可能必须在某些线程使用时锁定变量。还是有其他一些聪明的方法来实现它?

【问题讨论】:

  • 如有疑问,请将其锁定。如果您甚至在考虑是否要锁定……就锁定。读取或写入时,锁定您的共享状态。也就是说,有很多可能的方法可以解决这个问题,而多线程/共享状态是一个相当深奥的话题,所以我想说这个问题过于宽泛了。

标签: c# multithreading thread-safety


【解决方案1】:

如果您有一个Config 的共享实例并且所有内容都使用该实例,那么其他线程可能会在您读取它们时更改属性。为防止您可以lock 访问各个属性。但这不是一个令人愉快的想法。相反,最好尽量减少您不得不担心的情况。

一个步骤是让您的其他线程创建Config 的新实例,而不是全部共享。您的 GUI(表单?)可以接收一个全新的 Config 实例。

如果你有这样的事情(例如 - 一个属性是否应该是公共的,或者你的类如何接收Config 的实例是一个完全不同的问题。)

public Config MyConfigInstance { get; set; }

如果您通过提供具有不同值的 Config 的不同实例来设置该属性,则这是一个原子操作。您不必担心您的班级会在引用更新的中途访问MyConfigInstance

这可能就是您所需要的。但仍有出错的余地。现在你的类引用了Config 的新实例。但是其他哪些线程可以访问相同的引用,并且仍然可以更改它?至少将它传递给此类的线程仍然可以访问。如果那个线程传递了那个实例,而现在其他类已经引用了它怎么办?

这在您的应用程序中可能不是问题。也许您确切地知道哪些线程可以访问哪些类的实例。但如果事情变得更复杂,我们宁愿避免问题,也不愿尝试跟踪它。

为此,如果多个线程将共享一个类的实例,通常最好使该类不可变。是这样的:

public class MyImmutableClass
{
    public MyImmutableClass(string stringValue, int intValue)
    {
        StringValue = stringValue;
        IntValue = intValue;
    }

    public string StringValue { get; }
    public int IntValue { get; }
}

它的属性是在创建时设置的,不能更改。此时,您不必关心有多少线程引用了该类的实例,因为它们都无法更改它。您可以用对新实例的引用替换对一个实例的引用,但这不会影响其他引用。

不可变类的另一个好处是,如果值只在构造函数中设置,那么您可以验证它们。就像上面的例子一样,如果stringValue 为空,你可以抛出异常。这样,您始终知道该类处于有效状态。无法创建处于无效状态的实例。

我们倾向于创建可变类,因为创建读/写属性比创建构造函数更快。但是,如果我们要将实例传递给不同的线程,那么有时最好从不变性开始,这迫使我们思考和控制多个线程可以做什么或不可以做什么。

【讨论】:

    【解决方案2】:

    您正在寻找单例设计模式:

    public sealed class Config
    {
        private static Config instance = null;
        private static readonly object padlock = new object();
    
        #region ConfigProperties
        //put public properties here to represent config values
        public int a { get; set; }
        public Dictionary<int, int> b { get; set; }
        #endregion ConfigProperties
    
        Config()
        {
            ReadConfig();
        }
    
        public static Config Instance
        {
            get
            {
                lock (padlock)
                {
                    if (instance == null)
                    {
                        instance = new Config();
                    }
                    return instance;
                }
            }
        }
        public void ReadConfig()
        {
            //read from your config file and set properties
        }
        public void SaveConfig()
        {
            //write properties back into your config file
        }
    }
    

    此类是线程安全的,这意味着您可以使用Config.Instance. 后跟任何配置属性,并且可以安全地访问它们而不会导致跨线程问题。

    有更优雅的解决方案和更详细的解释here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-02-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多