繁体   English   中英

unique_lock 不同线程中的相同互斥锁

[英]unique_lock same mutex in different thread

我正在看这段代码:

#include <chrono>
#include <iostream>
#include <map>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <thread>

bool flag;
std::mutex m;

void wait_for_flag() {
  // std::cout << &m << std::endl;
  // return;
  std::unique_lock<std::mutex> lk(m);
  while (!flag) {
    lk.unlock();
    std::cout << "unlocked....." << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "sleeping....." << std::endl;
    lk.lock();
    std::cout << "locked by " << std::this_thread::get_id() << "....."
              << std::endl;
  }
}

int main(int argc, char const *argv[]) {
  std::thread t(wait_for_flag);
  std::thread t2(wait_for_flag);
  std::thread t3(wait_for_flag);
  std::thread t4(wait_for_flag);
  std::thread t5(wait_for_flag);

  t.join();
  t2.join();
  t3.join();
  t4.join();
  t5.join();

  return 0;
}

我是新手,我认为互斥锁只能由一个线程获取。 我有两个问题:

  1. 为什么这些线程之间没有死锁,例如如果线程 A 运行 lk.unlock(),那么线程 B 运行 lk.lock(),然后线程 A 运行 lk.lock()。
  2. 这是什么意思我们在每个线程中定义一个新的 unique_lock 关联到同一个互斥锁(在这里称为 m )

谢谢

因为在获得互斥锁上的锁之后,每个线程都会调用lk.unlock(); 现在其他线程可以获取互斥锁上的锁。 只有当一个线程试图锁定一个已经锁定的互斥锁(由不同的线程)时,它才必须等待互斥锁空闲。 因为代码中的任何线程最终都会调用lk.unlock(); 不同的线程总是有机会获得互斥锁上的锁并且没有死锁。

例如,如果您有两个互斥体并且两个线程尝试以不同的顺序锁定它们,则会发生死锁:

  // thread A
  std::unique_lock<std::mutex> lk1(mutex1);
  std::unique_lock<std::mutex> lk2(mutex2);    // X

  // thread B
  std::unique_lock<std::mutex> lk2(mutex2);
  std::unique_lock<std::mutex> lk1(mutex1);    // X

这里可能发生线程 A 锁定 mutex1,线程 B 锁定 mutex2,然后都在X中等待另一个线程释放另一个 mutex,但这永远不会发生。 这是一个僵局。

2.

锁只是一种纤细的 RAII 类型。 它的唯一目的是在创建时调用互斥lock的锁,并在销毁时unlock 您可以通过手动锁定/解锁互斥锁来编写没有锁的相同代码,但是当互斥锁被锁定时出现异常时,它将永远不会被解锁。

@SolomonSlow 我的问题是,如果我们使用 unique_lock 将互斥锁包装在不同的线程中,为什么没有死锁......?

“死锁”意味着有一些线程集,其中没有一个线程可以继续,直到该组的其他成员之一执行某些操作。 在最简单的死锁中,只有两个线程,并且有两个互斥锁:

  • 线程 A 在 mutex 1 上放置了unique_lock ,它被阻塞,等待在 mutex 2 上放置锁。
  • 线程 B 在 mutex 2 上加了锁,它被阻塞,等待在 mutex 1 上加锁。

在线程 B 先做某事之前,线程 A 不能做任何事情,而在线程 A 先做某事之前,线程 B 也不能做任何事情。 两个线程都无法再做任何事情。 僵局。

如果没有线程等待的至少两个不同的事物(例如,两个不同的互斥体),您就不会出现死锁。 如果只有一个互斥锁,那么无论哪个线程将其锁定,该线程都将能够继续。 没有线程能够继续时,这只是一个死锁。

在您的示例中,五个线程中的每一个都进入一个循环:

  • 解锁互斥锁,
  • 打印,睡眠,打印,
  • 锁定互斥锁,
  • 打印,
  • go 回到循环顶部。

每当您的一个线程锁定互斥锁时,没有什么可以阻止它打印然后返回顶部并再次解锁互斥锁,以便其他线程可以运行。 没有僵局。

这不是一个答案。 这只是一个插图。 我把你的一个例子变成了三个不同的例子,它们都达到了相同的结果。 我希望它可以帮助您更好地理解unique_lock的作用。

第一种方法根本不使用unique_lock 它只使用mutex 这是老派的方式——在RAII被发现之前我们做事的方式。

std::mutex m;

{
    ...
    while (...) {
      do_work_outside_critical_section();
      m.lock();                        // explicitly put a "lock" on the mutex.
      do_work_inside_critical_section();
      m.unlock();                      // explicitly remove the "lock."
    }
}

老式的方法是有风险的,因为如果do_work_inside_critical_section()抛出异常,它会将互斥锁留在锁定的 state 中,任何尝试再次锁定它的线程都可能永远挂起。


第二种方式使用unique_lock ,这是RAII的一个体现。

RAII 模式确保没有办法离开这个在互斥锁m上留下锁的代码块。 unique_lock析构函数总是会被调用,无论如何,析构函数会移除锁。

std::mutex m;

{
    ...
    while (...) {
      do_work_outside_critical_section();
      std::unique_lock<std::mutex> lk(m); // constructor puts a "lock" on the mutex.
      do_work_inside_critical_section();
    }                                     // destructor implicitly removes the "lock."
}

请注意,在此版本中,每次循环都会构造和销毁unique_lock 这听起来可能很昂贵,但实际上并非如此。 unique_lock旨在以这种方式使用。


最后一种方法是您在示例中所做的。 它只创建和销毁一次unique_lock ,但随后它会在循环内重复锁定和解锁它。 这可行,但它的代码行比上面的版本多,这使得阅读和理解有点困难。

std::mutex m;

{
    ...
    std::unique_lock<std::mutex> lk(m); // constructor puts a "lock" on the mutex.
    while (...) {
      lk.unlock();                      // explicitly remove the "lock" from the mutex.
      do_work_outside_critical_section();
      lk.lock();                        // explicitly put a "lock" back on the mutex.
      do_work_inside_critical_section();
    }
}                                       // destructor implicitly removes the "lock."

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM