为什么printf导致future.get死锁,而cout却没有呢?

时间:2019-10-09 17:31:13

标签: c++ multithreading c++11

我对MPI和CUDA有一定的经验,现在我决定是时候实际进行一些线程化了。我正在学习C ++标准库线程问题,并且(基于一系列Youtube视频)正在构建一个简单的代码,该代码使用std :: packaged_task构建作业并将其发送到作业队列以供工作线程执行。到目前为止很简单。

当我尝试通过将来检索作业结果时,问题开始了:

  printf_mutex.lock();
  printf("MAIN: Result of %i! is %i\n", 6, future_result_of_packaged_task.get()); // this causes deadlock!
  printf_mutex.unlock();

这将永远锁定代码! 但这有效:

  int mah_result = future_result_of_packaged_task.get();
  printf_mutex.lock();
  printf("MAIN: Result of %i! is %i\n", 6, mah_result ); // this is okay
  printf_mutex.unlock();

这样做(这是youtuber所做的):

std::cout << future_result_of_packaged_task.get() << "\n"; //this is okay

为什么COUT正确工作时PRINTF()为什么会失败?

我认为了解这个问题可能非常有教育意义。

整个代码非常简单(不需要某些库,因为我只是从以前的玩具代码中懒洋洋地复制了它们,但谁在乎呢?):

#include <cstdio>
#include <thread>
#include <string>
#include <mutex>
#include <condition_variable>
#include <iostream>
#include <future>
#include <deque>

int factorial(int N, std::mutex& printf_mutex)
{
  int result = 1;
  for (int i = N; i > 1; --i) result *= i;
  printf_mutex.lock();
  printf("FACTORIAL: Result of %i! is %i\n", N, result);
  printf_mutex.unlock();
  return result;
}

void worker_thread( std::deque< std::packaged_task<int()> >& task_queue, std::mutex& task_queue_mutex, std::condition_variable& task_queue_cv, std::mutex& printf_mutex )
{
  std::unique_lock<std::mutex> task_queue_mutex_lock(task_queue_mutex);
  task_queue_cv.wait(task_queue_mutex_lock, [&](){return !task_queue.empty();} );
  printf_mutex.lock();
  printf("WORKER: I'm not sleeping anymore!\n"); // this is okay
  printf_mutex.unlock();
  std::packaged_task<int()> my_task = std::move( task_queue.front() );
  task_queue.pop_front();
  my_task();
}

int main()
{
  std::mutex printf_mutex;
  std::mutex task_queue_mutex;
  std::deque< std::packaged_task<int()> > task_queue;
  std::condition_variable task_queue_cv;

  std::thread a_thread( worker_thread, std::ref(task_queue), std::ref(task_queue_mutex), std::ref(task_queue_cv), std::ref(printf_mutex) );
  std::this_thread::sleep_for(std::chrono::seconds(1));

  std::packaged_task<int()> a_task( bind(factorial, 6, std::ref(printf_mutex)) );
  std::future<int> future_result_of_packaged_task = a_task.get_future();

  task_queue_mutex.lock();
  task_queue.push_back(std::move(a_task));
  task_queue_mutex.unlock();
  task_queue_cv.notify_one();
  printf_mutex.lock();
  printf("MAIN: Notification sent!\n"); // this is okay
  printf_mutex.unlock();

  //std::cout << future_result_of_packaged_task.get() << "\n"; //this is okay

  int mah_result = future_result_of_packaged_task.get();
  printf_mutex.lock();
  printf("MAIN: Result of %i! is %i\n", 6, mah_result ); // this is okay
  printf_mutex.unlock();

  printf_mutex.lock();
  //printf("MAIN: Result of %i! is %i\n", 6, future_result_of_packaged_task.get()); // this causes a deadlock!
  printf_mutex.unlock();

  a_thread.join();
  return 0;
}

是的,我讨厌C ++ iostream,是的,我讨厌std :: locks,它们的存在侵犯了Occam的Razor。我还对玩具代码使用了可怕的命名方案。对于棘手的问题,这都不重要。

编辑:因此,从已接受的答案中,难题的解决方案并不明显。我想说清楚:  1.使用printf_mutex保护cout会使cout和printf一样失败。这表明问题可能是future.get()干扰了我机器上的输出机制,或者是互斥体冲突/竞赛。如有疑问,请始终怀疑种族,并注意:
 2. future.get()是一个阻止函数。我有效地锁定了一个互斥锁并入睡,这是一场比赛。那场比赛在哪里艰难进行?通过实验,我们知道它永远不会在工作线程中发生。还有什么可能发生?
3。答案是阶乘也尝试锁定printf_mutex并失败,因为主线程始终先锁定它,然后在将来进入休眠状态。get()

可接受的答案是提供最强/最完整线索的答案。

2 个答案:

答案 0 :(得分:3)

您持有printf_mutex,因此任务无法完成,future_result_of_packaged_task.get()再也不会返回。您的其他示例在调用get时不保留互斥体,因此请不要死锁。

答案 1 :(得分:0)

您的竞争条件介于worker_thread和最低的printf_mutex.lock()之间(您可以通过在std::this_thread::sleep_for(std::chrono::seconds(1));之前放置printf_mutex.lock();来查看它) ,很幸运,worker_thread获胜,您在factorial内部锁定和最低printf_mutex.lock()内部锁定之间遇到了互斥逻辑问题。 factorial将永远被锁定,因为它在printf_mutex被锁定之后被调用。您可以锁定在factorial内部,也可以锁定在外部printf之前。

无论是否使用std::coutprintf都是运气。 std::cout可以比printf慢或快,具体取决于您的优化指令。请注意,您之前使用的是std::cout,而之前没有printf_mutex.lock();,这就是为什么它起作用的原因。另外,在有效的printf情况下,您在锁定之前调用了.get(),这就是它起作用的原因。修复争用条件和锁定逻辑,然后printfstd::cout都可以工作。


不好。:最好使用RAII锁定模式,也可以考虑对同一线程锁定使用std::recursive_mutex,以便进行打印,