3

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

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

为此,我们有一个名为 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 库中?

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

4

2 回答 2

2

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

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

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

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

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

于 2013-03-21T14:02:00.943 回答
1

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

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

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

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

于 2013-03-21T13:53:59.583 回答