C++ 锁定相互引用的资源对
考虑以下几点:C++ 锁定相互引用的资源对,c++,multithreading,concurrency,synchronization,locking,C++,Multithreading,Concurrency,Synchronization,Locking,考虑以下几点: // There are guys: class Guy { // Each guy can have a buddy: Guy* buddy; // When a guy has a buddy, he is his buddy's buddy, i.e: // assert(!buddy || buddy->buddy == this); public: // When guys are birthed into
// There are guys:
class Guy {
// Each guy can have a buddy:
Guy* buddy; // When a guy has a buddy, he is his buddy's buddy, i.e:
// assert(!buddy || buddy->buddy == this);
public:
// When guys are birthed into the world they have no buddy:
Guy()
: buddy{}
{}
// But guys can befriend each other:
friend void befriend(Guy& a, Guy& b) {
// Except themselves:
assert(&a != &b);
// Their old buddies (if any), lose their buddies:
if (a.buddy) { a.buddy->buddy = {}; }
if (b.buddy) { b.buddy->buddy = {}; }
a.buddy = &b;
b.buddy = &a;
}
// When a guy moves around, he keeps track of his buddy
// and lets his buddy keep track of him:
friend void swap(Guy& a, Guy& b) {
std::swap(a.buddy, b.buddy);
if (a.buddy) { a.buddy->buddy = &a; }
if (b.buddy) { b.buddy->buddy = &b; }
}
Guy(Guy&& guy)
: Guy()
{
swap(*this, guy);
}
Guy& operator=(Guy guy) {
swap(*this, guy);
return *this;
}
// When a Guy dies, his buddy loses his buddy.
~Guy() {
if (buddy) { buddy->buddy = {}; }
}
};
到目前为止,一切都很好,但现在我希望在不同线程中使用伙伴时,这种方法也能起作用。没问题,让我们把std::mutex
放在Guy
:
class Guy {
std::mutex mutex;
// same as above...
};
现在我只需要在链接或取消链接这两个人之前锁定他们的互斥体
这就是我被难倒的地方。以下是失败的尝试(以析构函数为例):
~Guy() {
std::unique_lock<std::mutex> lock{mutex};
if (buddy) {
std::unique_lock<std::mutex> buddyLock{buddy->mutex};
buddy->buddy = {};
}
}
不幸的是,要访问buddy的互斥锁,我们必须访问buddy
,此时它不受任何锁的保护,并且可能正在从另一个线程进行修改,这是一种竞争条件static std::mutex mutex;
~Guy() {
std::unique_lock<std::mutex> lock{mutex};
if (buddy) { buddy->buddy = {}; }
}
static std::mutex mutex;
~Guy(){
std::unique_lock{mutex};
如果(伙伴){buddy->buddy={};}
}
但出于性能和可伸缩性的原因,这是不可取的std::lock
将使用无死锁算法获得两个锁。它们将是某种(未指明的)尝试和撤退的方法
另一种方法是确定任意锁顺序,例如使用对象的物理地址
您已经正确地排除了对象伙伴本身的可能性,因此没有尝试两次将相同的互斥锁锁定()的风险
我说这本身不是一个竞争条件,因为代码将确保完整性,如果a有buddy b,那么b有buddy a代表所有a和b
事实上,在与两个对象成为朋友后的某一刻,它们可能会被另一个线程取消朋友关系,这可能是您的意图或其他同步所要解决的问题
还要注意的是,当你与新朋友的朋友交朋友或解除朋友关系时,你需要一次锁定所有对象。
这是两个“未来”朋友和他们现在的朋友(如果有的话)。
因此,您需要锁定2、3或4个互斥锁
std::lock
不幸的是,它不接受数组,但是有一个版本在boost中可以这样做,或者您需要手动解决它
为了澄清,我正在阅读可能的析构函数作为模型的例子。所有相关成员都需要同步相同的锁(例如,befriend()
,swap()
和unfriend()
,如果需要的话)。实际上,锁定2、3或4的问题适用于befriend()
成员
此外,析构函数可能是最糟糕的例子,因为正如在注释中提到的,一个对象是可破坏的,但可能与另一个线程存在锁争用,这是不合逻辑的。在更广泛的程序中肯定需要存在一些同步,以使析构函数中的锁在某一点上是冗余的,这是不可能的
事实上,确保Guy
对象在销毁之前没有好友的设计似乎是一个好主意,也是一个在析构函数中检查assert(buddy==nullptr)
的调试先决条件。遗憾的是,这不能作为运行时异常,因为在析构函数中抛出异常会导致程序终止(std::terminate()
)
事实上,真正的挑战(可能取决于周围的节目)是如何在交朋友时解除朋友。这似乎需要一个试退循环:
锁a和锁b
看看他们有没有朋友
如果他们已经是好朋友了,你就完蛋了
如果他们有其他伙伴,解锁a&b,并锁定a和b及其伙伴(如果有)
如果他们又去了,检查一下他们是否没有改变
调整相关成员
对于周围的程序来说,这是一个问题,它是否会有活锁风险,但任何尝试-退出方法都会有同样的风险
不起作用的是std::lock()
a&b,然后std::lock()
buddies,因为这样会有死锁的风险
所以要回答这个问题-是的,没有全局锁是可能的,但这取决于周围的程序。它可能在许多Guy
对象的群体中,竞争很少见,而且活跃度很高。
但可能是因为有少量的对象被激烈争夺(可能是在大量人群中),导致了问题。如果不了解更广泛的应用,就无法对其进行评估
解决这一问题的一种方法是锁升级,它实际上是一个全局锁。本质上,这意味着如果在重试循环中有太多的行程,那么将设置一个全局信号量,命令所有线程进入全局锁模式一段时间。一段时间可能是一系列操作或一段时间,或者直到全局锁上的争用平息
因此,最后的答案是“是的,这是绝对可能的,除非它不起作用,在这种情况下‘不’”。我认为你需要将“佛性”和“男性”区分开来。i、 e.一个名为“Guys”的新类,其中包含对零个、一个或两个Guys的引用。@RichardHodges,这有什么帮助?同样的问题也存在于家伙和每个家伙之间。我有点同意@richardhoges。从概念上讲,似乎需要同步的是Guy
s之间的关系,而不是Guy
s本身。这可以通过家伙
之间共享的互斥锁来实现,每当好友关系被切断时(无论是新的家伙
成为朋友还是被摧毁),互斥锁都会被锁定。我非常喜欢这个挑战。我对“哲学家进餐”问题持批评态度,因为虽然它显示了死锁,但它不是典型系统的非常现实的模型,因为锁的数量与线程的数量相同,这是一个非典型问题。我想知道应用程序可能是什么。我所能想到的就是一套
static std::mutex mutex;
~Guy() {
std::unique_lock<std::mutex> lock{mutex};
if (buddy) { buddy->buddy = {}; }
}