提问人:André Caldas 提问时间:11/11/2023 最后编辑:UselessAndré Caldas 更新时间:11/17/2023 访问量:201
传递锁所有权
Passing lock ownership
问:
在 RAII 中,我们有 std::unique_lock 和 std::scoped_lock。这两个是明确可移动的。RAII 对象在构造时被“锁定”,在被破坏时被“解锁”。因此,我认为绝对没有真正的理由禁止将“所有权”传递给不同的线程。如果它不正确或无用,人们可以对将unique_ptr传递到另一个线程说同样的话。
我想做这样的事情:
std::scoped_lock lock{mutex};
// Do some stuff.
// Decide to multithread...
auto lambda = [lock = std::move(lock)]
{
// Do stuff with the lock.
};
std::thread{std::move(lambda)}.detach();
现在,我有两个问题:
- 我错过了什么?这没有意义吗?
- 在 cppreference 对 std::scoped_lock的描述中,我没有看到任何关于不在其他线程中解锁的内容。所以,我想知道标准是否禁止我上面发布的代码。
答:
TL;博士
您绝对可以将锁从一个线程移动到另一个线程,只要底层 Lockable 支持它。但是,没有一个标准库互斥锁支持它。
因此,我认为绝对没有真正的理由禁止将“所有权”传递给不同的线程。
原则上,我也没有,有几个警告:
- 即使它有意义,如果标准是保守的或限制性的,或者希望为一些狡猾的平台特定优化留出实现空间,它也可能是 UB
- 某些特定的互斥锁类型(如递归互斥锁)可能需要存储当前线程 ID,因此无法处理此用例。
综上所述,我们可以检查标准:
1/ 执行代理是一个实体,例如线程,可以与其他执行代理并行执行工作。
[注 1:实现或用户可以引入其他类型的代理,例如进程或线程池任务。 — 尾注]
[注 2:一些可锁定对象是“代理遗忘的”,因为它们适用于任何执行代理模型,因为它们不确定或存储代理的 ID(例如,普通的旋转锁)。 — 尾注]
因此,听起来我们当然可以自由地使用我们自己的“执行代理”抽象,并且当我们的抽象从一个线程移动到另一个线程时,至少一些可锁定的类型应该可以正常工作。
不幸的是,没有一个标准互斥锁类型属于这一类:它们都指定它们必须由“拥有”互斥锁的线程解锁,拥有互斥锁的唯一方法是锁定它,并且没有描述转移所有权的机制。
但是,您可以基于信号量、自旋锁或其他任何未明确禁止这样做的类型编写自己的 Cpp17Lockable 类型,并认为自己正式受到该标准的祝福。
在 RAII 中,我们有 std::unique_lock 和 std::scoped_lock。这两个是明确可移动的。RAII 对象在构造时被“锁定”,在被破坏时被“解锁”。因此,我认为绝对没有真正的理由禁止将“所有权”传递给不同的线程。
是的,就 and 而言,将所有权转移到另一个线程是没有问题的。这是因为这些课程专注于一项任务——锁的 RAII。std::unique_lock
std::scoped_lock
这些类对互斥锁的操作方式一无所知。他们所需要的只是包装对象(例如互斥锁)是“(基本)可锁定的”——它具有成员函数,并且在某些情况下具有 .锁类确保对 的调用与对 的调用匹配,并且对返回的调用与对 的调用匹配。这些类不关心这些函数的效果是什么;重要的是在需要时调用它。lock()
unlock()
try_lock()
lock()
unlock()
try_lock()
true
unlock()
unlock()
与其查看 RAII 包装器,不如查看正在包装的内容。如果要包装 std::mutex
,则适用以下情况。
调用线程从成功调用 EITHER 或直到调用 时拥有 。
mutex
lock
try_lock
unlock
这就是为什么您不能将 a 的所有权转移到另一个线程的原因。锁定的线程将拥有互斥锁,直到它调用 。不仅直到(被某人)调用,而且直到该特定线程调用 .使用 时,不能将此任务委派给另一个线程。std::mutex
std::mutex
unlock
unlock
unlock
std::mutex
另一个 Lockable 的行为可能不同,并允许在线程之间转移所有权,但事实并非如此。std::mutex
“不要这样做”的真正原因是,这是非常规的。如果你曾经期望其他程序员阅读你的代码——如果你曾经期望与他们合作,或者你曾经向其他程序员寻求帮助——那么如果每个人都说同一种语言,你会有更好的运气。
“互斥锁”不仅仅是某些编程语言中的类型名称。它是设计模式的名称。如果你在代码中使用了一种叫做“互斥锁”的东西,那么每个看到它的人都会立即期望你以某种方式使用它。他们不会喜欢和你一起工作或帮助你。
您要执行的操作有不同的名称。它被称为二进制信号量。它的作用几乎与互斥锁完全相同,只是,如果您将其锁定在一个线程中并将其锁定在另一个线程中,没有人会扬眉吐气。
* “Mutex”说,“我只是要在这里访问一些共享数据,我会尽可能快地处理它。
“信号量”说,“嘿!拿着我的啤酒,看着这个。
为了补充前面的答案:必须从被调用的线程调用的原因是因为或其 Windows 等效项具有此约束。最后,这确实是互斥锁定义的一部分。unlock
lock
pthread_mutex_unlock
评论
[c++]