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

发布于 2024-09-16 02:07:27 字数 543 浏览 9 评论 0原文

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

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

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

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

我已经将参考文献设为易失性:

public volatile data live
public volatile data backup

I have a data class with lots of data in it (TV schedule data).
The data is queried from one side and periodically updated from the other side.
There are two threads: the first thread queries the data on request and the second thread updates the data on regular intervals.
To prevent locking, I use two instances (copies) of the data class: the live instance and the backup instance.
Initially, both instances are filled with the same data. The first thread only reads from the live instance.
The second thread periodically updates both instances as follows:

  • Update the backup instance.
  • Swap the backup and live instance (i.e. the backup instance becomes the live instance).
  • Update the backup instance.
  • Both backup instance and live instance are now up-to-date.

My questions is: how should I use the volatile keyword here?

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

I have already made the references volatile:

public volatile data live
public volatile data backup

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

乙白 2024-09-23 02:07:28

我认为用 易失性 标记东西不会得到你想要的效果。考虑这段代码。

volatile data live;

void Thread1()
{

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

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

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

Joe Albahari 电子书中的高级线程部分 详细介绍了 易失性 的语义,并且应该解释为什么您只需要来标记您的实时参考。

I do not think you are going to get the effect you want by marking things with volatile. Consider this code.

volatile data live;

void Thread1()
{

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

In the example above false could be written to the console if the second thread swapped the live and backup references between the time the first thread entered the if and called Console.WriteLine.

If that problem does not concern you then all you really need to do is mark the live variable as volatile. You do not need to mark the individual fields in data as volatile. The reason is because volatile reads create acquire fence memory barriers and volatile writes create release fence memory barriers. What that means is that when thread 2 swaps the references then all writes to the individual fields of data must commit first and when thread 1 wants to read the individual fields of the live instance the live variable must be reacquired from main memory first. You do not need to mark the backup variable as volatile because it is never used by thread 1.

The advanced threading section in Joe Albahari's ebook goes into a great deal of detail on the semantics of volatile and should explain why you only need to mark your live reference as such.

回忆那么伤 2024-09-23 02:07:27

如果您打算在lock之外修改字段,或者不使用Interlocked,则应将字段声明为易失性。这是深入解释易失性的最佳文章:http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/

fields should be declared volatile if you plan to modify them outside locks, or without Interlocked. Here is the best article that explain volatile deeply: http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/

夕嗳→ 2024-09-23 02:07:27

说实话,我只会锁定它。正确性更容易检查,并且无需备份。

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

public class Data
{
  public int SimpleInt;
}

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

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

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

考虑字段的字段也必须是易失性的这一点也很重要。

实际上,现在您只需要一个存储的 Data 对象。新的对象将被创建为仅由一个线程引用的对象(因此它不会被另一个线程损坏或对其造成损坏),并且其创建将基于从 live 读取的值,即同样安全,因为另一个线程只是读取(除非某些记忆技术,这意味着“读取”实际上是在幕后写入,读取不会损害其他读取,尽管它们可能会受到写入的损害)在仅对单个线程可见的情况下进行更改线程,因此只有最后的写入才需要担心同步,这确实应该安全,只有 volatile 或用于保护的 MemoryBarrier ,因为分配引用是原子的,并且因为您不再关心旧值。

To be honest, I would just lock on it. The correctness is so much easier to check, and the need for the backup is removed.

With your plan here, the fields would also have to be volatile. Consider the case otherwise:

public class Data
{
  public int SimpleInt;
}

Here we have just a single public field for simplicity, the same applies to more realistic structures. (Incidentally, captials for class names is a more common convention in C#).

Now consider live.SimpleInt as seen by thread A. Because live could be cached, we need to have it as volatile. However, consider that when the object is swapped with backup, and then swapped back to live, then live will have the same memory location as it did before (unless the GC has moved it). Therefore live.SimpleInt will have the same memory location as it did before, and therefore if it was not volatile, thread A may be using a cached version of live.SimpleInt.

However, if you created a new Data object, rather than swapping in and out, then the new value of live.SimpleInt will not be in the thread's cache, and it could be safely non-volatile.

It's also important to consider that the fields of the fields will have to be volatile too.

Indeed now you need just one stored Data object. The new one will be created as an object referenced only by one thread (hence it cannot be damaged by or do damage to another thread), and its creation will be based on values read from live, which is also safe as the other thread is only reading (barring some memoisation techniques that mean that "reads" are really writes behind the scenes, reads can't harm other reads, though they can be harmed by writes) altered while visible to just a single thread, and hence only the final write requires any concern about synchronisation which should indeed be safe with only volatile or a MemoryBarrier used for protection, since assigning a reference is atomic, and since you don't care about the old value anymore.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文