【问题标题】:How to make a static variable thread-safe如何使静态变量线程安全
【发布时间】:2012-10-19 19:31:11
【问题描述】:

我有这个静态类,它包含一个静态变量(一个简单的 int)。我在线程的Run() 方法中实现了lock(),所以没有其他线程可以同时访问这个类,但是变量仍然疯狂,显示重复、异常高的值等等。

这是课程:

public static class ExplorationManager
{
    public static int Counter = 0;

    public static void ExplorerMaker(List<int[]> validPaths, List<string> myParents, string[,] myExplorationMap, List<int[]> myPositions)
    {
        foreach (var thread in validPaths.Select
        (path => new Explorer(myParents, path, myExplorationMap, myPositions)).
        Select(explorer => new Thread(explorer.Explore)))
            {
                thread.Name = "Thread of " + Counter + " generation";
                Counter++; 
                thread.Start();
    }
}

}

有没有办法让这个变量“更加”线程安全?

【问题讨论】:

  • 发疯了,显示重复,数值高得离谱 - 无法从这段代码中得到真正的解释。

标签: c# multithreading


【解决方案1】:

为了提高这种类型的安全性,您至少需要解决 2 个问题。

第一个是制作Counterprivate。在当前的形式中,该变量是 100% 公开的,它可以被应用程序中的任何代码改变。今天它可能是安全的,但没有什么可以保护你明天不犯错误。如果您仍然希望其他代码能够读取该属性,请使用访问器

private static int m_counter;
public static int Counter {
  get { return m_counter; }
}

第二个问题是++ 不是对线程间共享位置的安全操作。它扩展为以下代码

Counter = Counter + 1;

实际上是在做什么

  1. 加载计数器
  2. 加载 1
  3. 添加
  4. 专卖店柜台

线程几乎可以随时中断。如果一个线程在步骤 1、2 或 3 被中断,而另一个线程完全执行该序列,那么您最终将添加/存储陈旧的值。这就是++ 不安全的原因。在线程之间增加共享值的安全方法是使用Interlocked.Increment。它正是为此目的而设计的

Interlocked.Increment(ref m_counter);

【讨论】:

  • 如果这不起作用,是我的代码有问题吗?或者还有其他方法可以实现吗?
【解决方案2】:

使用Interlocked 类:

Interlocked.Increment(ref Counter);

【讨论】:

    【解决方案3】:

    您需要在静态变量的所有读/写操作中使用lock。比如:

    public static readonly object CounterLock = new object();
    
    ...
    lock ( CounterLock )
    {
        Counter++;
    }
    ...
    

    关键是 所有 读/写 必须由锁保护 - 保护一个地方是不够的,因为执行读或写的线程仍然可能使当其他地方的锁生效时的更改。

    锁保护的是代码区域,而不是变量,这就是为什么在访问共享变量的任何地方都需要一个锁。

    请注意,您不能锁定 Counter 变量 - 您需要引用类型的实例作为锁定,而不是值类型。这就是我使用object 作为锁定类型的原因(另一个答案也是如此)。

    【讨论】:

    • 我不知道。我认为保护对类的访问不受Run() 方法的影响,实际上是保护了它。谢谢。
    • @XaweryWiśniowiecki 是的,你是对的。我会更新答案。谢谢。
    【解决方案4】:

    这样的事情应该可以解决问题:

    public static class ExplorationManager
    {
        public static int Counter = 0;
        private static object _lock = new object();
    
        public static void ExplorerMaker(List<int[]> validPaths, List<string> myParents, string[,] myExplorationMap, List<int[]> myPositions)
        {
            foreach (var thread in validPaths.Select
            (path => new Explorer(myParents, path, myExplorationMap, myPositions)).
            Select(explorer => new Thread(explorer.Explore)))
                {
                    thread.Name = "Thread of " + Counter + " generation";
                    lock(_lock)
                    {
                        Counter++; 
                        thread.Start();
                    }
        }
    }
    

    【讨论】:

      【解决方案5】:

      Interlocked.Increment 是另一个线程安全选项。如果您只需要计数器,使用起来非常简单。

      var newCounter = Interlocked.Increment(ref Counter)
      thread.Name = "Thread of " + (newCounter-1) + " generation";
      

      【讨论】:

        【解决方案6】:

        您可以尝试使用静态构造函数来初始化静态变量。最好提供一个单独的 locking 对象,这样您就可以很好地控制锁的粒度。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-18
          • 1970-01-01
          • 2011-04-05
          • 1970-01-01
          • 2014-12-24
          相关资源
          最近更新 更多