简体   繁体   English

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

[英]unique_lock same mutex in different thread

i am looking at this piece of code:我正在看这段代码:

#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;
}

I am new to this, and I thought mutex can only be acquired by one thread.我是新手,我认为互斥锁只能由一个线程获取。 I got two questions:我有两个问题:

  1. why there is no deadlock among those threads, eg if thread A runs lk.unlock(), then thread B runs lk.lock() and then thread A runs lk.lock().为什么这些线程之间没有死锁,例如如果线程 A 运行 lk.unlock(),那么线程 B 运行 lk.lock(),然后线程 A 运行 lk.lock()。
  2. what does it mean we define a new unique_lock in every thread associating to the same mutex lock (which is called m in here)这是什么意思我们在每个线程中定义一个新的 unique_lock 关联到同一个互斥锁(在这里称为 m )

Thanks谢谢

Because right after acquiring a lock on the mutex each thread calls lk.unlock();因为在获得互斥锁上的锁之后,每个线程都会调用lk.unlock(); and now other thread can acquire a lock on the mutex.现在其他线程可以获取互斥锁上的锁。 Only if a thread tries to lock an already locked mutex (by a different thread) it has to wait for the mutex to be free.只有当一个线程试图锁定一个已经锁定的互斥锁(由不同的线程)时,它才必须等待互斥锁空闲。 As any thread in your code eventually calls lk.unlock();因为代码中的任何线程最终都会调用lk.unlock(); there is always a chance for a different thread to get a lock on the mutex and there is no deadlock.不同的线程总是有机会获得互斥锁上的锁并且没有死锁。

A deadlock would occur for example if you have two mutexes and two threads try to lock them in different order:例如,如果您有两个互斥体并且两个线程尝试以不同的顺序锁定它们,则会发生死锁:

  // 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

Here it can happen that thread A locks mutex1, thread B locks mutex2 and then both wait in X for the other thread to release the other mutex, but this will never happen.这里可能发生线程 A 锁定 mutex1,线程 B 锁定 mutex2,然后都在X中等待另一个线程释放另一个 mutex,但这永远不会发生。 Its a deadlock.这是一个僵局。

2. 2.

A lock is merely a slim RAII type.锁只是一种纤细的 RAII 类型。 Its only purpose is to call lock on the mutex when created and unlock when destroyed.它的唯一目的是在创建时调用互斥lock的锁,并在销毁时unlock You can write the same code without the lock, by manually locking / unlocking the mutex, but when there is an exception while a mutex is locked it will never be unlocked.您可以通过手动锁定/解锁互斥锁来编写没有锁的相同代码,但是当互斥锁被锁定时出现异常时,它将永远不会被解锁。

@SolomonSlow my question is, if we use unique_lock to wrap the mutex in different threads, why there is no deadlock...? @SolomonSlow 我的问题是,如果我们使用 unique_lock 将互斥锁包装在不同的线程中,为什么没有死锁......?

"Deadlock" means that there is some set of threads in which none of the threads can proceed until one of the other members of the set does something. “死锁”意味着有一些线程集,其中没有一个线程可以继续,直到该组的其他成员之一执行某些操作。 In the simplest possible deadlock, there are just two threads, and there are two mutexes:在最简单的死锁中,只有两个线程,并且有两个互斥锁:

  • Thread A has placed a unique_lock on mutex 1, and it is blocked, waiting to place a lock on mutex 2.线程 A 在 mutex 1 上放置了unique_lock ,它被阻塞,等待在 mutex 2 上放置锁。
  • Thread B has placed a lock on mutex 2, and it is blocked, waiting to place a lock on mutex 1.线程 B 在 mutex 2 上加了锁,它被阻塞,等待在 mutex 1 上加锁。

Thread A can't do anything until thread B does something first, and thread B can't do anything until thread A does something first.在线程 B 先做某事之前,线程 A 不能做任何事情,而在线程 A 先做某事之前,线程 B 也不能做任何事情。 Neither thread will ever be able to do anything again.两个线程都无法再做任何事情。 Deadlock.僵局。

You can't have a deadlock without at least two different things (eg, two different mutexes) that the threads wait for.如果没有线程等待的至少两个不同的事物(例如,两个不同的互斥体),您就不会出现死锁。 If there's only one mutex, then whichever thread has it locked, that thread will be able to proceed.如果只有一个互斥锁,那么无论哪个线程将其锁定,该线程都将能够继续。 It's only a deadlock when no thread is able to proceed.没有线程能够继续时,这只是一个死锁。

In your example, each of the five threads settles in to a loop:在您的示例中,五个线程中的每一个都进入一个循环:

  • unlock the mutex,解锁互斥锁,
  • print, sleep, print,打印,睡眠,打印,
  • lock the mutex,锁定互斥锁,
  • print,打印,
  • go back to the top of the loop. go 回到循环顶部。

Whenever one of your threads locks the mutex, there's nothing to stop it from printing and then going back to the top and unlocking the mutex again so that some other thread can run.每当您的一个线程锁定互斥锁时,没有什么可以阻止它打印然后返回顶部并再次解锁互斥锁,以便其他线程可以运行。 There's no deadlock.没有僵局。

This is not an answer.这不是一个答案。 It's just an illustration.这只是一个插图。 I turned your one example into three different examples that all achieve the same result.我把你的一个例子变成了三个不同的例子,它们都达到了相同的结果。 I hope it may help you to better understand what unique_lock does.我希望它可以帮助您更好地理解unique_lock的作用。

The first way doesn't use unique_lock at all.第一种方法根本不使用unique_lock It only uses the mutex .它只使用mutex This is the old-school way—the way we used to do things before RAII was discovered.这是老派的方式——在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."
    }
}

The old-school way is risky because if do_work_inside_critical_section() throws an exception, it will leave the mutex in a locked state, and any thread that tries to lock it again probably will hang forever.老式的方法是有风险的,因为如果do_work_inside_critical_section()抛出异常,它会将互斥锁留在锁定的 state 中,任何尝试再次锁定它的线程都可能永远挂起。


The second way uses unique_lock , which is an embodiment of RAII .第二种方式使用unique_lock ,这是RAII的一个体现。

The RAII pattern ensures that there's no way out of this code block that leaves a lock on mutex m . RAII 模式确保没有办法离开这个在互斥锁m上留下锁的代码块。 The unique_lock destructor always will be called, no matter what, and the destructor removes the lock. 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."
}

Notice that in this version, a unique_lock is constructed and destructed every time around the loop.请注意,在此版本中,每次循环都会构造和销毁unique_lock That might sound costly, but it really isn't.这听起来可能很昂贵,但实际上并非如此。 unique_lock is meant to be used in this way. unique_lock旨在以这种方式使用。


The last way is what you did in your example.最后一种方法是您在示例中所做的。 It only creates and destroys the unique_lock one time, but then it repeatedly locks and unlocks it within the loop.它只创建和销毁一次unique_lock ,但随后它会在循环内重复锁定和解锁它。 This works, but it's more code lines than the version above, which makes it a little bit harder to read and understand.这可行,但它的代码行比上面的版本多,这使得阅读和理解有点困难。

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