关于使用 std::unique_lock 的说明

Posted

技术标签:

【中文标题】关于使用 std::unique_lock 的说明【英文标题】:clarification on using std::unique_lock 【发布时间】:2020-04-02 13:42:22 【问题描述】:

所以,我有一个 std::map 对象,它同时被多个线程访问,我决定使用 unique_lock 来确保映射操作安全。

在一个线程的实现中,地图对象被一些函数使用(这些函数通常从地图对象中添加/删除项目),所以我想知道在父函数的顶部定义 unique_lock 是否可以保证安全 ?或者我需要将它添加到这些功能中的每一个?

void Thread1() 
  std::unique_lock<std::mutex> ul(mutex);

  func1();  // map object is getting changed here
  func2();  // map object is getting changed here


【问题讨论】:

只要一个互斥锁被锁定,它就不能被任何其他试图锁定同一个互斥锁的线程锁定。因此,这可能是一个“同步惩罚”,它会减慢您的并发执行速度。锁应尽可能紧密地封闭对共享数据的每次访问。您可以公开minimal reproducible example 以获得更具体的答案。 一般情况下,您希望在尽可能靠近临界区的位置创建和销毁unique_lock。这意味着将其放在每个func1func2 中,仅围绕访问您的地图的部分。确保也将它放在读取访问权限周围(或者使用读/写锁,而不是使用底层shared_mutex 以及shared_lockunique_lock 的组合来获取访问权限)。 感谢@Scheff 和 JohnFilleau 的回复。但是抛开代码优化不谈,这个逻辑能保证安全吗? 嗯。对抗比赛条件的安全性?是的。死锁怎么办?一个线程尝试写入,但在填充缓冲区时从不解锁。另一个线程尝试读取但正在等待锁定。失败是安全的(从用户的角度来看)。而且,顺便说一句。如果我不打算通过并发来获得性能/加速... ;-) 另外,您可以为每个线程提供一个单独的映射(或缓冲区或其他)来填充,并在加入所有线程后合并结果。所以,你不需要任何同步。 (除了在每个线程的启动和加入时您已经拥有(并且无法阻止)的那个),即您不需要互斥锁。对于您的问题,这可能是最安全、甚至最快的解决方案。 【参考方案1】:

使用互斥锁的全部意义在于防止线程 B、C、D 等由于线程 A 所做的更改而看到处于不一致状态的共享数据。

你提出了这个:

void Thread1() 
    std::unique_lock<std::mutex> ul(mutex);
    func1();  // map object is getting changed here
    func2();  // map object is getting changed here

func1() 是否让地图(可能还有其他共享变量)处于其他线程不应该看到的状态? func2() 是否让其他线程再次可以查看共享数据?如果是这样,那么您的示例可能是您所希望的最好的示例。但是,如果这两个函数调用中的每一个都使共享数据处于“安全”状态,那么您可以考虑让它们分别锁定和解锁mutex

如果线程可以设计成不经常需要访问共享数据,并且当他们确实需要访问共享数据时,你通常会更好,他们尽快进出(即锁定和解锁)。


另请参阅,readers writer locking(在 C++ 中,std::shared_lock)以防万一有助于解决您的特定问题。

【讨论】:

【参考方案2】:

我看到你在这里的东西真的行不通。如果每个线程都试图在操作开始时获取互斥锁,要么你有死锁或只有一个线程可以运行的顺序线程,要么你有多个不同的互斥锁。后者是不正确的,但前者放弃了多线程的一些好处。

如果您对必须锁定的函数使用更明确的作用域,则可以使用锁定而不直接将它们添加到正在使用的函数中。

void Thread1() 
funcA();
// Explicit scoping
std::unique_lock<std::mutex> ul(mutex);

func1();  // map object is getting changed here
func2();  // map object is getting changed here

funcB();


我们希望在尽可能短的时间内使用锁。因此,您必须判断更改地图对象的函数是否足够小以证明在整个时间内锁定是合理的。

【讨论】:

以上是关于关于使用 std::unique_lock 的说明的主要内容,如果未能解决你的问题,请参考以下文章

C++11:互斥锁std::mutex和std::lock_guard/std::unique_lock

使用std::lock 和 std::unique_lock来起先swap操作

linux C++互斥锁std::lock_guard(轻锁)std::unique_lock(重锁)区别

linux C++互斥锁std::lock_guard(轻锁)std::unique_lock(重锁)区别

与 std::conditional_variable::wait() 一起使用时 std::unique_lock 的工作是啥 [重复]

在不同的函数中锁定/解锁 std::unique_lock