传递锁所有权

问题描述 投票:0回答:1

在某些地方,人们会询问如何在一个线程中锁定互斥体并在其他线程中解锁。当然,那些人会受到很多“不要这样做”、“这是不正确的”、“你不知道自己在做什么”的轰炸。

参见: 解锁被另一个线程锁定的互斥体在一个线程中锁定互斥量并在另一个线程中解锁 .

人们倾向于认为锁是由线程拥有的。因此,尝试从其他线程解锁它是不正确的。我认为没有理由相信锁必须由某个线程拥有。

嗯...当您有标准支持时,说起来很容易,明确禁止在一个线程中锁定并在其他线程中解锁。但人们没有意识到,事情并不是因为事情就是这样,所以就不能有所不同。顺便说一下,我说的是STL中的互斥体。说实话,我没有阅读该标准。 :-)

但我确实认为以下说法是正确的:

[...] 这是因为解锁由另一个线程锁定的互斥体将允许任何人访问受该互斥体保护的资源,同时仍然执行依赖于单手访问这些资源这一事实的代码。因此,这种情况下的行为是未定义的。

如果线程 A 在线程 B 到达 mutex_lock(2) 之前到达 mutex_unlock(2),则您将面临未定义的行为。您也不能解锁另一个线程的互斥锁。

我有一些可能很长的原子操作要执行。我使用互斥锁来锁定它。通常它们不长。但在该过程的中间,我可能决定在为此目的而创建的新线程中继续处理。当然,“锁所有权”应该传递给新线程而不需要解锁!!!

使用 RAII,我们有 std::unique_lockstd::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();

现在我有两个问题:

我错过了什么吗?这没有道理吗?
  1. cppreference 对 std::scoped_lock 的描述中,我没有看到任何提及不在不同线程中解锁的内容。所以,我想知道标准是否禁止我上面发布的代码。
multithreading mutex
1个回答
0
投票

公平地说,在您链接的特定问题中,发帖者不知道自己在做什么,而这些警告是非常正确的。

我有一些可能很长的原子操作要执行。

如果您正在讨论多线程、未定义的行为和所有事物的标准,请尝试使用正确的术语。原子这个词意味着与多线程相关的非常具体的东西,但它不是这个。

所以,我认为绝对没有真正的理由禁止将“所有权”传递给不同的线程。

原则上我也不同意,但有一些警告:

如果标准是保守的或限制性的,或者想要为某些狡猾的平台特定优化留出实现空间,即使它有意义,它也可能是 UB

    某些特定的互斥体类型,例如递归互斥体,可能需要存储当前线程 ID,因此无法应对此用例。
  1. 综上所述,我们可以检查标准:

[thread.req.lockable]



执行代理是一个实体,例如可以与其他执行代理并行执行工作的线程。

[注1: 实现或用户可以引入其他类型的代理,例如进程或线程池任务。 — 尾注]

[注2: 一些可锁定对象是“代理无意识的”,因为它们适用于任何执行代理模型,因为它们不确定或存储代理的ID(例如,普通的自旋锁)。 — 尾注]

因此,听起来我们可以自由地编写自己的“执行代理”抽象,并且当我们的抽象从一个线程移动到另一个线程时,至少一些可锁定类型应该可以正常工作。

不幸的是,没有一个标准互斥体类型属于此类:它们都指定必须由“拥有”互斥体的线程解锁,拥有互斥体的唯一方法是锁定它,并且没有转移所有权的机制已描述。

但是,您可以基于信号量、自旋锁或任何其他未明确禁止此操作的内容编写自己的

Cpp17Lockable

类型,并认为自己受到标准的正式祝福。

© www.soinside.com 2019 - 2024. All rights reserved.