守护数据设计模式

时间:2013-03-21 13:24:03

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

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

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

为此,我们有一个名为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; }
};

再次,一个非常简单的概念。运营商 - &gt;模仿STL迭代器的语义以访问有效负载。

现在我想知道:

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

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

2 个答案:

答案 0 :(得分:2)

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

另外,你的GuardedData对象如何传递? boost :: lock_guard不可复制 - 它会增加互斥锁的所有权问题,即&amp;当它被释放时。

可能更容易将所需的部分数据复制到读取器/写入器线程所需的时间,从而使关键部分保持简短。作者同样会一次性承诺数据模型。

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

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

答案 1 :(得分:1)

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

您的方法很适合一次保护一个数据项,但如果您需要保护更多,则会变得很麻烦。此外,它看起来不像你的设计会阻止我在共享的地方创建这个对象并随意访问数据,认为它保护得很好,但实际上递归访问方案没有处理,也没有多线程访问方案(如果它们出现在同一范围内)。

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

真的,这不是一个糟糕的方法,但你需要确保理解它的用途。