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