为什么不能检查互斥锁是否已锁定?


28

C ++ 14似乎省略了一种检查an是否std::mutex被锁定的机制。看到这样的问题:

https://stackoverflow.com/questions/21892934/how-to-assert-if-a-stdmutex-is-locked

有几种解决方法,例如通过使用;

std::mutex::try_lock()
std::unique_lock::owns_lock()

但是这些都不是特别令人满意的解决方案。

try_lock()如果当前线程已锁定互斥锁,则允许返回假阴性,并且具有未定义的行为。它也有副作用。owns_lock()需要unique_lock在原始的基础上进行构造std::mutex

显然我可以自己动手,但我宁愿了解当前界面的动机。

std::mutex::is_locked()对我来说,检查互斥对象状态的能力似乎并不神秘,因此我怀疑标准委员会故意忽略了此功能,而不是疏忽大意。

为什么?

编辑:好的,也许这个用例并不像我预期的那么普遍,所以我将举例说明我的特殊情况。我有一个分布在多个线程上的机器学习算法。每个线程异步运行,并在完成优化问题后返回到主池。

然后锁定主互斥锁。然后,该线程必须选择一个新的父代来从其变异后代,但只能从当前没有其他线程正在优化的后代的父代中选择。因此,我需要执行搜索以找到当前未被另一线程锁定的父母。由于主线程互斥锁已锁定,因此互斥锁的状态不会在搜索过程中发生变化。显然,还有其他解决方案(我目前正在使用布尔标志),但是我认为互斥锁为该问题提供了一种逻辑解决方案,因为它存在于线程间同步的目的。


42
您不能真正合理地检查互斥锁是否被锁定,因为检查后的一纳秒可以将其解锁或锁定。因此,如果您编写了“ if(mutex_is_locked())...”,则mutex_is_locked可以返回正确的结果,但是在执行“ if”时,这是错误的。
gnasher729

1
这个^。您希望从中获得什么有用的信息is_locked
没用的2016年

3
感觉像是XY问题。您为什么只在生孩子时才试图防止父母重用?您是否要求任何父母只能生育一个孩子?您的锁不会阻止这种情况。你没有几代人吗?如果不是,您是否知道可以更快地优化个人,因为他们可以更频繁地/更早地被选拔,所以他们具有更高的适应性?如果您使用世代,为什么不先选择所有父代,然后让线程从队列中检索父代呢?生成后代真的那么昂贵,需要多个线程吗?
阿蒙(Amon)2013年

10
@quant-我完全看不到为什么示例应用程序中的父对象互斥锁根本不需要是互斥锁:如果您有一个主互斥锁,只要设置了该主互斥锁,它们就可以使用布尔变量来指示其状态。
佩里亚塔·布雷塔

4
我不同意问题的最后一句话。一个简单的布尔值远比互斥锁干净。如果您不想为“返回”父级而锁定主互斥锁,则使其成为原子布尔。
塞巴斯蒂安·雷德尔

Answers:


53

我可以看到建议的操作至少存在两个严重问题。

@ gnasher729在评论中已经提到了第一个:

您不能真正合理地检查互斥锁是否被锁定,因为检查后的一纳秒可以将其解锁或锁定。因此,如果您编写了代码,if (mutex_is_locked ()) …mutex_is_locked可以返回正确的结果,但是在if执行时,这是错误的。

确保互斥锁的“当前锁定”属性不变的唯一方法是自己锁定。

我看到的第二个问题是,除非您锁定了一个互斥锁,否则您的线程不会与之前锁定该互斥锁的线程同步。因此,谈论“之前”和“之后”甚至没有明确的定义,并且互斥锁是否被锁定只是在询问Schrödiger的猫是否还活着而没有尝试打开盒子的情况。

如果我理解正确,那么由于主互斥锁被锁定,在您的特定情况下这两个问题都将无法解决。但这对我来说似乎不是一个特别常见的情况,因此我认为委员会做对了事,没有添加一项功能,该功能在非常特殊的情况下可能会有些用处,并且会给所有其他情况造成损害。(本着:“使界面易于正确使用而难以错误使用的精神。”)

如果我可以说的话,我认为您当前拥有的设置并不是最优雅的,可以对其进行重构以完全避免该问题。例如,为什么主线程不维护所有准备就绪的父级队列,而不是主线程检查所有潜在的父级当前未被锁定的父级?如果线程要优化另一个线程,则将下一个线程弹出队列,并在有了新的父线程后立即将其添加到队列中。这样,您甚至不需要主线程作为协调器。


谢谢,这是一个很好的答案。我不想保留准备就绪的父母队列的原因是,我需要保留创建父母的顺序(因为这决定了他们的寿命)。使用LIFO队列很容易做到这一点。如果我开始拉进和拉出东西,则必须维护一个单独的排序机制,这会使事情复杂化,因此是当前的方法。
定量

14
@quant:如果你有两个目的队列的父母,你可以这样做2分队列....

@quant:您最多一次删除一个项目,但大概每次都要删除一次,因此您要以罕见的情况为代价来优化稀有情况。这很少是可取的。
杰里·科芬

2
但是询问当前线程是否已锁定互斥锁合理的。
有限赎罪

@LimitedAtonement不是。为此,互斥锁必须存储其他信息(线程ID),从而使其变慢。递归互斥已经做到了,您应该改为使用它们。
StaceyGirl

9

似乎您正在使用辅助互斥锁不是锁定对优化问题的访问,而是要确定是否正在优化某个优化问题。

那完全没有必要。我将列出需要优化的问题,现在正在优化的问题,以及已经优化的问题。(不要从字面上理解“列表”,而应指“任何适当的数据结构”)。

将新问题添加到未优化问题列表或将问题从一个列表移至下一个列表的操作将在单个“主”互斥锁的保护下完成。


1
您认为类型的对象不std::mutex适合这种数据结构吗?
定量

2
@quant-不 std::mutex依赖于操作系统定义的互斥体实现,该互斥体实现可能会占用有限且分配和/或操作缓慢的资源(例如,句柄)。使用单个互斥锁来锁定对内部数据结构的访问可能会更加高效,并且可能还会具有更大的可伸缩性。
佩里亚塔·布雷塔

1
还考虑条件变量。他们可以像这样使很多数据结构变得非常容易。
Cort Ammon-恢复莫妮卡

2

正如其他人所说,is_locked互斥量没有任何用处的用例,这就是为什么该功能不存在的原因。

您遇到的问题的情况下是非常常见的,它基本上是什么工作线程做什么,这是一个,如果不是线程的最常见的实现。

您有一个带10个盒子的架子。您有4个工人在使用这些箱子。您如何确保4名工人在不同的箱子上工作?第一个工人在开始工作之前将一个盒子从架子上拿下来。第二个工人看到架子上有9个盒子。

没有用于锁定盒子的互斥锁,因此不必查看盒子上虚构的互斥锁的状态,并且滥用互斥锁作为布尔值是错误的。互斥锁锁定架子。


1

除了上述5gon12eder回答中给出的两个原因外,我还要补充一点,这既不是必需的也不是理想的。

如果您已经持有一个互斥锁,那么您最好知道自己正在持有它!你不用问 就像拥有一块内存或任何其他资源一样,您应该确切知道自己是否拥有它,以及什么时候适合释放/删除资源。
如果不是这种情况,则说明您的程序设计不良,并且您正走向麻烦。

如果您需要访问互斥锁保护的共享资源,并且尚未持有该互斥锁,则需要获取该互斥锁。没有其他选择,否则您的程序逻辑不正确。
无论哪种情况,您都可能会发现阻止是可接受的还是不可接受的,lock()或者try_lock()会给出您想要的行为。毫无疑问,您肯定要知道的是,您是否成功获取了互斥锁(try_lock告诉您的返回值)。别人是否拥有它或您是否有虚假的失败都是无关紧要的。

坦率地说,在任何其他情况下,这都不关您的事。您不需要知道,也不应该知道,也可以做一些假设(针对另一个问题中提到的及时性和同步性问题)。


1
如果我想对当前可用于锁定的资源执行排名操作,该怎么办?
2016年

但这是现实发生的事情吗?我认为那很不寻常。我想说,要么资源已经具有某种内在的排名,然后您就需要首先进行(获取锁定)更重要的排名。示例:必须在渲染之前更新物理模拟。或者,排名或多或少是有意的,那么您也可以try_lock选择第一种资源,如果该资源失败,请尝试第二种资源。示例:与数据库服务器的三个持久的池化连接,您需要使用一个来发送命令。
达蒙

4
@quant-“对当前可用于锁定的资源进行排名操作”-通常,执行这种操作是一种非常容易快捷的方式来编写代码,而这种方式会陷入僵局,您很难弄清楚。在几乎所有情况下,确定锁的获取和释放是最好的策略。根据可能更改的标准搜索锁正在引起麻烦。
佩里亚塔·布雷塔

@PeriataBreatta我的程序是故意不确定的。现在,我看到此属性并不常见,因此我可以理解省略诸如此类的功能is_locked()可能会促进这种行为。
定量

@quant排名和锁定是完全独立的问题。如果您想以某种方式对带有锁的队列进行排序或重新排序,请对其进行锁定,排序然后解锁。如果需要is_locked,那么比您所想到的解决方案更好的解决方案是存在的。
彼得

1

您可能要使用默认存储顺序的atomic_flag。它没有数据争用,并且从不抛出像互斥锁那样的具有多个解锁调用的异常(并且无法控制地中止,我可能会添加...)。另外,还有原子的(例如atomic [bool]或atomic [int](带尖括号,而不是[])),它具有不错的功能,如load和compare_exchange_strong。


1

我想为此添加一个用例:它将启用内部函数,以确保调用者确实持有该锁作为前提/断言。

对于具有多个这样的内部函数以及可能有许多调用它们的公共函数的类,可以确保有人添加了另一个调用内部函数的公共函数确实获得了锁。

class SynchronizedClass
{

public:

void publicFunc()
{
  std::lock_guard<std::mutex>(_mutex);

  internalFuncA();
}

// A lot of code

void newPublicFunc()
{
  internalFuncA(); // whops, forgot to acquire the lock
}


private:

void internalFuncA()
{
  assert(_mutex.is_locked_by_this_thread());

  doStuffWithLockedResource();
}

};
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.