【问题标题】:Guarded Data Design Pattern受保护的数据设计模式
【发布时间】:2013-03-11 00:58:12
【问题描述】:

在我们的应用程序中,我们处理在工作线程中处理并在显示线程中访问的数据,并且我们有一个负责关键部分的互斥锁。没什么特别的。

现在我们考虑重新编写代码,其中当前锁定由持有和处理数据的一方明确完成。我们想到了一个单独的实体来保存数据,并且只允许以受保护的方式访问数据。

为此,我们有一个名为 GuardedData 的类。调用者可以请求这样一个对象,并且应该在本地范围内只保留很短的时间。只要对象存在,它就会保持锁定。一旦对象被销毁,锁就会被释放。数据访问与锁定机制相结合,调用者无需进行任何明确的额外工作。班级的名字让来电者想起了现在的守卫。

template<typename T, typename Lockable>
class GuardedData {
    GuardedData(T &d, Lockable &m) : data(d), guard(m) {}
    boost::lock_guard<Lockable> guard;
    T &data;

    T &operator->() { return data; }
};

再次,一个非常简单的概念。 operator-> 模仿 STL 迭代器的语义来访问有效负载。

现在我想知道:

  • 这种方法众所周知吗?
  • 是否有类似这样的模板类已经可用,例如在 boost 库中?

我问是因为我认为这是一个相当通用且可用的概念。不过我找不到类似的东西。

【问题讨论】:

  • 我有可以在后台更改的数据,而在前台访问它。实际上,当后台计算完成时交换数据。当有人访问数据时不应发生这种交换,这可能导致该方进入不一致的状态。我们需要进行交换,而不是仅仅创建一个新实例,而其他人仍在使用旧实例。原因是大量数据会同时驻留在内存中。

标签: c++ design-patterns concurrency mutex guard


【解决方案1】:

根据它的使用方式,您几乎肯定会在某些时候陷入死锁。如果您想对两条数据进行操作,那么您最终会锁定互斥锁两次并死锁(除非每条数据都有自己的互斥锁 - 如果锁定顺序不一致也会导致死锁 - 您无法控制用这个方案而不使它变得非常复杂)。除非您使用可能不需要的递归互斥锁。

另外,您的 GuardedData 对象是如何传递的? boost::lock_guard 不可复制 - 它会引发互斥体的所有权问题,即释放位置和时间。

在需要时将所需的部分数据复制到读取器/写入器线程可能更容易,同时保持关键部分简短。作者将类似地一次性提交数据模型。

基本上,您的查看器线程会在给定时间获取所需数据的快照。这甚至可能完全适合位于运行线程的核心附近的 cpu 缓存,并且永远不会进入 RAM。编写器线程可以在阅读器处理底层数据时修改它(但这应该使视图无效)。但是,由于查看器有一个副本,因此它可以继续并提供与数据同步时的数据视图。

另一个选项是给视图一个指向数据的智能指针(应该被视为不可变的)。如果作者希望修改数据,它会在此时复制它,修改副本,完成后,将指针切换到模型中的数据。这将需要在处理时阻止所有读取器/写入器,除非只有 1 个写入器。下次阅读器请求数据时,它会得到新的副本。

【讨论】:

  • 所以第二次迭代将使用 boost::recursive_mutex 而不是 lock_guard。
  • 有问题的数据对象可以达到几千兆字节,因此避免冗余副本很重要。
  • 是否需要每次都复制所有数据?允许外部客户端管理锁定可能最终会出现问题。
  • 我过去处理这个问题的一种方法是为包含一段数据的数据设置一个缓冲迭代器。这真的取决于您通常如何访问数据 - 范围或随机单个元素?
  • 嗯,通常大部分数据都被触及或最后一个区域,尽管大多数操作只是为了读取和产生其他东西而触及它。一些操作确实改变了整个数据,例如数据范围的标准化。
【解决方案2】:

众所周知,我不确定。但是,我在 Qt 中使用了一种类似的机制,通常称为QMutexLocker。区别(一个次要的,恕我直言)是您将数据与互斥锁绑定在一起。与您描述的机制非常相似的机制是 C# 中的线程同步规范。

您的方法非常适合一次保护一个数据项,但如果您需要保护更多数据项,则会变得很麻烦。此外,您的设计看起来不会阻止我在共享位置创建此对象并尽可能频繁地访问数据,认为它的保护非常好,但实际上递归访问场景没有得到处理,也没有多线程访问场景,如果它们发生在同一范围内。

这个想法似乎有点脱节。它的使用向我传达了访问数据始终是线程安全的,因为数据受到保护。通常,这不足以确保线程安全。对受保护数据的操作顺序通常很重要,因此锁定实际上是面向范围的,而不是面向数据的。你可以在你的模型中通过保护一个虚拟对象并将你的保护对象包装在一个临时范围中来解决这个问题,但是为什么不只使用现有的互斥体实现呢?

确实,这不是一个坏方法,但您需要确保了解其预期用途。

【讨论】:

  • 我认为 QMutexLocker 本身与 boost::lock_guard 相同。它们都只是互斥锁获取/释放的 RAII/RRID 包装器。
猜你喜欢
  • 2012-01-31
  • 2011-08-03
  • 1970-01-01
  • 2014-05-21
  • 2017-11-25
  • 1970-01-01
  • 2013-06-20
  • 2013-04-20
  • 2012-01-08
相关资源
最近更新 更多