У меня есть некоторый опыт работы с 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
ПОЧЕМУ 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;
}
Да, я ненавижу iostream C ++ и да, я ненавижу std :: locks, их простое существование оскорбляет бритву Оккама. Я также использую ужасную схему именования для своих игрушечных кодов. Ничто из этого не имеет значения для жесткого вопроса.
РЕДАКТИРОВАТЬ: Таким образом, решение головоломки не очевидно из принятого ответа. Я хочу прояснить это: 1. Защита cout с помощью printf_mutex делает его неудачным, так же как и printf. Это говорит о том, что проблема заключается в том, что в будущем future.get () мешает работе выходных механизмов на моей машине, либо проблема заключается в столкновении мьютексов или гонке. В случае сомнений всегда подозревайте гонку и обратите внимание на то, что:
2. future.get () является функцией блокировки. Я эффективно заблокировал мьютекс и пошел спать, который просит гонки. Где может произойти эта гонка? Экспериментально мы знаем, что это никогда не происходит в рабочем потоке. Где еще это могло произойти?
3. Ответ заключается в том, что факториал также пытается заблокировать printf_mutex и терпит неудачу, потому что основной всегда сначала блокирует его, а затем переходит в спящий режим в будущем. / наиболее полная подсказка.