Почему printf вызывает взаимоблокировку с future.get, а cout - нет? - PullRequest
0 голосов
/ 09 октября 2019

У меня есть некоторый опыт работы с 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 и терпит неудачу, потому что основной всегда сначала блокирует его, а затем переходит в спящий режим в будущем. / наиболее полная подсказка.

Ответы [ 2 ]

3 голосов
/ 09 октября 2019

вы держите printf_mutex, поэтому задача не может быть завершена и future_result_of_packaged_task.get() никогда не возвращается. Другие ваши примеры не содержат мьютекс во время вызова get, поэтому не зацикливайтесь.

0 голосов
/ 09 октября 2019

У вас есть состояние гонки между 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::cout или с printf - это просто удача. std::cout может быть медленнее или быстрее, чем printf, в зависимости от вашей директивы оптимизации. И обратите внимание, что вы используете std::cout без printf_mutex.lock(); раньше, поэтому он работает. Кроме того, в случае printf, который работает, вы вызываете .get() перед блокировкой, поэтому он работает. Зафиксируйте условия гонки и логику блокировки, тогда сработают и printf, и std::cout.


Obs .: Предпочитают использовать схему блокировки RAII, а также рассмотреть возможность использования std::recursive_mutex для таких же блокировок резьбы, дляпечать, это может быть очень полезно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...