如何在此模型中使用volatile关键字?

时间:2010-08-23 12:43:22

标签: c# volatile

我有一个包含大量数据的数据类(电视节目表数据)。 从一侧查询数据并从另一侧定期更新。 有两个线程:第一个线程根据请求查询数据,第二个线程定期更新数据。 为防止锁定,我使用数据类的两个实例(副本):实时实例和备份实例。 最初,两个实例都填充相同的数据。第一个线程只从实时实例中读取。 第二个线程定期更新两个实例,如下所示:

  • 更新备份实例。
  • 交换备份和实时实例(即备份实例成为实时实例)。
  • 更新备份实例。
  • 备份实例和实时实例现在都是最新的。

我的问题是:我应该如何在这里使用volatile关键字?

public class data
{
  // Lots of fields here.
  // Should these fields also be declared volatile?
}

我已经将引用设为volatile:

public volatile data live
public volatile data backup

3 个答案:

答案 0 :(得分:0)

如果您计划在volatile之外或lock之外修改

字段,则应将

字段声明为Interlocked。以下是深度解释volatile的最佳文章:http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/

答案 1 :(得分:0)

说实话,我会锁定它。正确性更容易检查,并且删除了对备份的需求。

根据您的计划,这些字段也必须是不稳定的。考虑其他情况:

public class Data
{
  public int SimpleInt;
}

为简单起见,我们只有一个公共字段,这同样适用于更现实的结构。 (顺便提一下,类名称的captials是C#中更常见的约定。)

现在考虑线程A看到的live.SimpleInt。因为live可以被缓存,我们需要将它作为volatile。但是,请考虑当对象与backup交换,然后交换回live时,live将具有与之前相同的内存位置(除非GC移动了它) )。因此live.SimpleInt将具有与之前相同的内存位置,因此如果它不是易失性的,则线程A可能正在使用live.SimpleInt的缓存版本。

但是,如果您创建了一个新的Data对象,而不是交换进出,那么live.SimpleInt的新值将不在该线程的缓存中,并且它可以安全地非易失性。

同样重要的是要考虑字段的字段也必须是易变的。

确实,现在您只需要一个存储的Data对象。新的将被创建为仅由一个线程引用的对象(因此它不会被另一个线程损坏或损坏),并且它的创建将基于从live读取的值,这也是安全的另一个线程只是读取(禁止一些记忆技术,这意味着“读取”实际上是在幕后写入,读取不会损害其他读取,尽管它们可能会被写入损害)改变,只有单个线程可见,并且因此,只有最终写入才需要关于同步的任何问题,只有易失性或用于保护的MemoryBarrier才能确保安全,因为分配引用是原子的,因为你不再关心旧值了。

答案 2 :(得分:0)

我认为你不会通过volatile标记来获得你想要的效果。考虑一下这段代码。

volatile data live;

void Thread1()
{

  if (live.Field1)
  {
    Console.WriteLine(live.Field1);
  }
}

在上面的示例中,如果第二个线程在第一个线程进入false之间交换了livebackup引用,则可以将if写入控制台。叫Console.WriteLine

如果该问题与您无关,那么您真正需要做的就是将live变量标记为volatile。您无需将data中的各个字段标记为volatile。原因是易失性读取会创建获取围栅内存障碍,而易失性写入会创建释放围栏内存障碍。这意味着当线程2交换引用时,对data的各个字段的所有写入必须首先提交,并且当线程1想要读取live实例的各个字段时{{1}必须首先从主内存重新获取变量。您无需将live变量标记为backup,因为它永远不会被线程1使用。

advanced threading section in Joe Albahari's ebook详细介绍了volatile的语义,并解释了为什么您只需要标记volatile引用。