简体   繁体   English

C ++ 11线程挂起锁定互斥锁

[英]C++11 thread hangs on locking mutex

Using C++11 std::thread , std::mutex , I'm writing a simple worker thread. 使用C ++ 11 std::threadstd::mutex ,我正在编写一个简单的工作线程。 However, I got a strange hang issue in locking std::mutex , which looks like both threads (main and worker thread) are trying to lock a mutex, but both are blocked. 但是,我在锁定std::mutex了一个奇怪的挂起问题,看起来两个线程(主线程和工作线程)都试图锁定互斥锁,但两者都被阻止。

Here's the full code 这是完整的代码

#include <thread>
#include <condition_variable>
#include <memory>
#include <iostream>
#include <list>
std::condition_variable cv;
std::mutex m;
std::thread t;
bool shouldExit = false;
std::list<int> jobs;

void thread_func()
{
  std::unique_lock<std::mutex> lock(m);
  while (!shouldExit) {
    while (jobs.empty() && !shouldExit) {
      cv.wait(lock);
    }
    // Do some stuff
    if (jobs.empty()) {
      continue;
    }
    // Get a job and do something with it
    if (!lock.owns_lock()) {
      lock.lock();  // <<<< Worker thread hang here
    }
    auto j = std::move(jobs.front());
    jobs.pop_front();
    lock.unlock();
    std::cout << "Do something with job " << j << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }
}

int main()
{
  t = std::thread(thread_func);

  for (int i = 1; i < 100; ++i) {
    std::cout << "Push to job " << i << std::endl;
    {
    std::lock_guard<std::mutex> lock(m); // <<<< main thread hang here
    jobs.push_back(i);
    cv.notify_one();
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // To wait for thread exit
  shouldExit = true;
  cv.notify_one();
  t.join();
  return 0;
}

I'm compiling the code using below command on Ubuntu 14.04 我正在使用Ubuntu 14.04上面的命令编译代码

g++ -std=c++11 -g -O0 -pthread -o testthread testthread.cpp

The executing result is typically like this: 执行结果通常如下:

$ ./testthread
Push to job 1
Do something with job 1
Push to job 2
Do something with job 2
Push to job 3
Push to job 4

The interesting part is, when I move one line code of sleeping-1ms in main thread into the lock_guard like below, the issue is gone. 有趣的是,当我将主线程中的lock_guard -1ms的一行代码移动到如下所示的lock_guard ,问题就消失了。

  for (int i = 1; i < 100; ++i) {
    std::cout << "Push to job " << i << std::endl;
    {
    std::lock_guard<std::mutex> lock(m);
    jobs.push_back(i);
    cv.notify_one();
    std::this_thread::sleep_for(std::chrono::milliseconds(1)); // Moved into lock_guard
    }
  }

I could not figure out why. 我无法弄清楚为什么。 Could you help to explain the behavior of the code and what I did wrong? 你能帮忙解释一下代码的行为以及我做错了什么吗?

[Update] I know re-writing the worker thread in certain way could fix the issue. [更新]我知道以某种方式重写工作线程可以解决问题。 But I still would like to know in the original code what exactly happens when the two threads are locking the mutex but both are blocked. 但是我仍然想在原始代码中知道当两个线程锁定互斥锁但两者都被阻塞时究竟发生了什么。

It is undefined behavior to call cv.wait with lock not locked. 这是不确定的行为叫cv.waitlock没有被锁定。 Add this assert: 添加此断言:

while (!shouldExit) {
  assert(lock.owns_lock());    // <------ add this
  while (jobs.empty() && !shouldExit) {
    cv.wait(lock);
  }

libc++ will throw from the wait if !lock.owns_lock() , but I don't know what other implementations will do. libc ++将从wait if !lock.owns_lock()抛出,但我不知道其他实现会做什么。

You have serious and classic bugs in your code.... 您的代码中存在严重且经典的错误....

First, please see the annotated/numbered comments. 首先,请参阅带注释/编号的注释。 I will refer to them 我会提到他们

void thread_func()
{
  std::unique_lock<std::mutex> lock(m);        // <---- {1}
  while (!shouldExit) {                        // <---- {2}
    while (jobs.empty() && !shouldExit) {      // <---- {3}
      cv.wait(lock);
    }
    // Do some stuff
    if (jobs.empty()) {
      continue;
    }

    if (!lock.owns_lock()) {
      lock.lock();                             // <---- {4}
    }
    auto j = std::move(jobs.front());
    jobs.pop_front();
    lock.unlock();                             // <---- {5}
    std::cout << "Do something with job " << j << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }
}

{1} This is good.... see you defeating the aim of this in {5} {1}这很好......看到你在{5}中击败了这个目标

{2} shouldExit should be an atomic bool. {2} shouldExit应该是一个原子布尔。 Else you will have race conditions 否则你将有竞争条件

{3} At some point in execution, this condition will be tested while not holding the lock, see the unlock statement in {5}. {3}在执行的某个时刻,将在未持有锁的情况下测试此条件,请参阅{5}中的unlock语句。 Hence, you have yet another race condition. 因此,你还有另一种竞争条件。

{4} With an unlocked mutex, between the time you test the lock and the time you issue lock, the mutex could be acquired, causing this to perpetually wait here. {4}使用解锁的互斥锁,在您测试锁定的时间和发出锁定的时间之间,可以获取互斥锁,从而使其永久等待。

{5} Makes the mutex unlocked for the next execution of the loop... serious race conditions and deadlock will happen. {5}使互斥锁解锁以便下次执行循环......严重的竞争条件和死锁将会发生。

Patching up your current solution... {Bad but working patch... read further} 修补当前的解决方案... {Bad but working patch ...进一步阅读}

Just add lock.lock() to the last line to your thread_func() 只需添加lock.lock()的最后一行到你的thread_func()

like this.... 像这样....

void thread_func()
{
    .....more code omitted
    ........
    lock.unlock();                            
    std::cout << "Do something with job " << j << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(1));

    lock.lock();   //YOUR NEW LINE
  }
}

The addition restores the loop back to the original state of a mutex being locked before entry.... Note that there is another code path to reach the entry of the loop... where you had continue statement... Whenever a std::condition_variable::wait() returns, the lock is always relocked, so invariant is still maintained... 添加将循环恢复到在进入之前被锁定的互斥锁的原始状态....注意,有另一个代码路径来到达循环的条目...在哪里有continue语句...每当std::condition_variable::wait()返回,锁总是重新锁定,所以仍然保持不变性...

Now your code works!! 现在您的代码有效!! Yay!!! 好极了!!! ... But it still smells a lot! ......但它仍然闻起来很多! std::cout is thread-safe but the output isn't synchronized, therefore, you may have interleaved characters... std::cout是线程安全的,但输出不同步,因此,您可能有交错字符...

Sidelining the problem with std::cout How to do it properly? 使用std::cout解决问题如何正确执行? Check this code (also please see the comments) 检查此代码(另请参阅注释)

void thread_func()
{
    std::unique_lock<std::mutex> lock(m);
    while (!shouldExit)    // this is redundant, so I removed it in the final code
    {
        while (jobs.empty() && !shouldExit)
        {
            cv.wait(lock, []{ return !jobs.empty(); } );
        }
        // Do some stuff
        auto j = std::move(jobs.front());
        jobs.pop_front();
        //cout is thread-safe but not synchronized
        //std::cout << "Do something with job " << j << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
}

In most common cases I know of, its always better to test your "ready to proceed" conditions inside std::condition_variable::wait() . 在我所知道的最常见的情况下,最好在std::condition_variable::wait()测试你的“准备好继续”条件。


To put it all together for you.... Here is a better version 把它们放在一起为你......这是一个更好的版本

#include <thread>
#include <condition_variable>
#include <memory>
#include <iostream>
#include <list>
#include <atomic>

std::condition_variable cv;
std::mutex m;
std::mutex mxa;   //for std::cout locking
std::thread t;
std::atomic<bool> shouldExit;
std::list<int> jobs;

namespace detail
{

    std::ostream& safe_print()
    {
        return std::cout;
    }

    template<typename T, typename... Args>
    std::ostream& safe_print(T&& t, Args&&... args)
    {
        std::cout << t;
        return safe_print(std::forward<Args>(args)...);
    }
}

template<typename... Args>
std::ostream& println(Args&&... args)
{
    std::lock_guard<std::mutex> lck(mxa);
    auto&& x = detail::safe_print(std::forward<Args>(args)...);
    std::cout << std::endl;
    return x;
}

void thread_func()
{
    std::unique_lock<std::mutex> lock(m);
    while (jobs.empty() && !shouldExit)
    {
        cv.wait(lock, []{ return !jobs.empty(); } );
    }
    // Do some stuff
    auto j = std::move(jobs.front());
    jobs.pop_front();
    //std::cout << "Do something with job " << j << std::endl;
    println("Do something with job ", j);
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
}

int main()
{
    shouldExit = false;
    //safe_print("This is really funny ", 43, '\n');
    t = std::thread(thread_func);

    for (int i = 1; i < 100; ++i)
    {
        //std::cout << "Push to job " << i << std::endl;
        println("Push to Job ", i);
        {
            std::lock_guard<std::mutex> lock(m); // <<<< main thread doesn't hang here again
            jobs.push_back(i);
            cv.notify_one();
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }

    // To wait for thread exit
    shouldExit = true;
    cv.notify_one();
    t.join();
    return 0;
}

Try rewrite your worker thread like this: 尝试重写你的工作线程,如下所示:

void thread_func()
{
  while (!shouldExit) 
  {
    int j ;
    {  
      std::unique_lock<std::mutex> lock(m);  // lock object inside while
      while (jobs.empty() && !shouldExit) {
        cv.wait(lock);
      }
      // Do some stuff
      if (jobs.empty()) {
        continue;
      }
      j = jobs.front(); 
      jobs.pop_front();
    } // lock goes out of scope
    std::cout << "Do something with job " << j << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }
}

As you can see j is no longer moved, you could encapsulate the section after int j ; 正如您所看到的,j不再被移动,您可以在int j ;之后封装该部分int j ; inside a function to get the same effect. 在函数内部获得相同的效果。

The main idea of the rewrite is avoid messing with lock's member function and use it by letting constructor/destructor making the locking job. 重写的主要思想是避免搞乱lock的成员函数,并通过让构造函数/析构函数进行锁定作业来使用它。

Seems to work... 似乎工作......

You have a problem here: 你有一个问题:

   while (jobs.empty() && !shouldExit) {
      cv.wait(lock);
    }
    // Do some stuff
    if (jobs.empty()) {
      continue;
    }

When you wake up, you own the lock. 当你醒来时,你拥有锁。 However, by calling continue you loose any chance of releasing it. 然而,通过调用continue你没有任何释放它的机会。

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

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