mercredi 9 octobre 2019

Why does printf causes deadlock with future.get while the cout does not?

I have some experience with MPI and CUDA and now I decided that it is high time to actually do some threading. I was learning the C++ Standard Library threading thingies and (based on a series of Youtube videos) I was building a simple piece of code which builds a job with std::packaged_task and sends it to job queue for worker threads to execute. Simple enough so far.

The problem started when I tried retrieving a result of the job via a future:

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

This locks the code forever! But this works:

  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();

As does this (which is what the youtuber did):

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

WHY DOES PRINTF() FAIL WHILE THE COUT WORKS CORRECTLY?

I think understanding this problem could be very educational.

The entire code is simple enough (some libraries are not needed since i just lazy copypasted them from a previous toy code, but who cares):

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

Yes, I hate C++ iostream and yes i hate the std::locks, their mere existence offends the Occam's Razor. I also use horrible naming scheme for my toy codes. None of that matters for the question tough.

Aucun commentaire:

Enregistrer un commentaire