在.NET中使用volatile实现线程间单向通信

6

我读了不少关于这个主题的文章,我认为我终于理解了在C#中如何使用volatile关键字。但我还是想在这里问一下,以确保我正确地理解了这些概念。请考虑以下代码:

class ThreadWrapper
{
  public bool Enabled { get; set; }

  private void ThreadMethod()
  {
    while (Enabled) { // ... Do work }
  }
}

据我所了解,如果另一个线程将 Enabled=false 设置为 false,则原始线程可能无法立即(或永远)看到此更改。为确保编译器不会优化对 Enabled 属性的访问并依赖缓存值,更改代码如下应该可以解决问题。
  public bool Enabled { get {return mEnabled;} set {mEnabled=value;} }
  private volatile bool mEnabled;

现在,每当读取Enabled值时,它都保证获取到最新的值,是吗?如果是这样,我应该能够将其用作简单的信号或标志(如上所述)。

另一个例子是:

class C
{
  private volatile int i;
  private volatile bool enabledA;
  private volatile bool enabledB;

  void ThreadA()
  {
    i = 0; // Reset counter
    while (enabledA)
    {
      // .. Do some other repeated work
      if (i > 100)
      {
        // .. Handle threshold case
      }
    }
  }

  void ThreadB()
  {
    while (enabledB)
    {
      // .. Do work
      if (condition) // Some condition occurs that we want to count
      {
        i++;
      }
    }
  }
}

对于像这样的简单线程间通信,volatile 是否足够?我知道 i++ 不是原子操作,但如果一个线程在做所有递增,而另一个线程只是想读取它以查看是否达到了某个阈值,那么我们没问题,对吗?
编辑:在事后我当然找到了另一个类似的问题及其详细答案。

1
我喜欢在需要基于每个对象进行标记,且不想为每个对象使用锁的情况下使用volatile。例如,我的一些批处理应用程序具有多个线程,在列表中处理对象,并且有一个线程在完成对象时写出对象。我只需在工作线程完成给定对象后将volatile值翻转即可,写入线程在遍历列表时等待该值旋转/休眠即可。 - Bengie
2个回答

5

3

是的,这应该是安全的,也是volatile关键字的预期使用之一。但请注意,您不应该仅仅为了测试条件而循环;如果您只想等待它发生变化,请使用适当的同步原语以避免浪费CPU时间。还要注意,这个技术之所以有效,是因为只有一个线程在写;如果多个线程尝试增加计数器,则可能会丢失计数器命中(因为读-修改-写不是原子性操作)。


我认为这些是很好的观点。从我所了解的来看,volatile 只在像上面那样简单的情况下才真正有用。我正在学习如何安全地进行多线程编程,这可能是一个相当棘手的任务。 - Jeremy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接