我有一个包含大量数据的数据类(电视节目表数据)。 从一侧查询数据并从另一侧定期更新。 有两个线程:第一个线程根据请求查询数据,第二个线程定期更新数据。 为防止锁定,我使用数据类的两个实例(副本):实时实例和备份实例。 最初,两个实例都填充相同的数据。第一个线程只从实时实例中读取。 第二个线程定期更新两个实例,如下所示:
我的问题是:我应该如何在这里使用volatile关键字?
public class data
{
// Lots of fields here.
// Should these fields also be declared volatile?
}
我已经将引用设为volatile:
public volatile data live
public volatile data backup
答案 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
之间交换了live
和backup
引用,则可以将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
引用。