Блокировка с использованием std :: mutex для защиты cout в нескольких потоках - PullRequest
0 голосов
/ 27 мая 2018

Использование cout в нескольких потоках может привести к чередующемуся выводу.
Поэтому я попытался защитить cout с помощью мьютекса.

Следующий код запускает 10 фоновых потоков с помощью std :: async.Когда поток запускается, он печатает "Запущенная тема ...".Основной поток перебирает фьючерсы фоновых потоков в том порядке, в котором они были созданы, и выводит «Done thread ...» после завершения соответствующего потока.

Вывод синхронизируется правильно, но через некоторое времяпотоки начались, а некоторые закончили (см. вывод ниже), возникает тупик.Все оставшиеся фоновые потоки и основной поток ожидают мьютекс.

В чем причина тупика?

Когда функция печати завершена или одна итерация цикла for завершается,lock_guard должен разблокировать мьютекс, чтобы один из ожидающих потоков мог продолжить работу.

Почему все темы остались голодными?

Код

#include <future>
#include <iostream>
#include <vector>

using namespace std;
std::mutex mtx;           // mutex for critical section

int print_start(int i) {
   lock_guard<mutex> g(mtx);
   cout << "Started thread" << i << "(" << this_thread::get_id() << ") " << endl;
   return i;
}

int main() {
   vector<future<int>> futures;

   for (int i = 0; i < 10; ++i) {
      futures.push_back(async(print_start, i));
   }

   //retrieve and print the value stored in the future
   for (auto &f : futures) {
      lock_guard<mutex> g(mtx);
      cout << "Done thread" << f.get() << "(" << this_thread::get_id() << ")" << endl;
   }
   cin.get();
   return 0;
}

Выход

Started thread0(352)
Started thread1(14944)
Started thread2(6404)
Started thread3(16884)
Done thread0(16024)
Done thread1(16024)
Done thread2(16024)
Done thread3(16024)

Ответы [ 3 ]

0 голосов
/ 27 мая 2018

Ваша проблема заключается в использовании future::get:

Возвращает значение, сохраненное в общем состоянии (или выдает его исключение), когда общее состояние готово.

Если общее состояние еще не готово (т. Е. Поставщик еще не установил свое значение или исключение), функция блокирует вызывающий поток и ждет, пока он не будет готов.

http://www.cplusplus.com/reference/future/future/get/

Таким образом, если поток за будущим еще не запущен, функциональные блоки завершаются, пока этот поток не завершится.Однако вы вступаете во владение мьютексом перед вызовом future::get, поэтому любой ожидаемый вами поток не сможет получить мьютекс для себя.

Это должно решить вашу проблему взаимоблокировки:

int value = f.get();
lock_guard<mutex> g(mtx);
cout << "Done thread" << value << "(" << this_thread::get_id() << ")" << endl;
0 голосов
/ 27 мая 2018

Хорошо, что причина была обнаружена из источника.Тем не менее, довольно часто ошибка, как это бывает, может быть не так легко найти.И причина может также отличаться.К счастью, в случае тупика вы можете использовать отладчик для его исследования.

Я скомпилировал и запустил ваш пример, затем после присоединения к нему с помощью gdb (gcc 4.9.2 / Linux), есть обратная трассировка (шумная реализациядетали пропущены):

#0  __lll_lock_wait ()
...
#5  0x0000000000403140 in std::lock_guard<std::mutex>::lock_guard (
    this=0x7ffe74903320, __m=...) at /usr/include/c++/4.9/mutex:377
#6  0x0000000000402147 in print_start (i=0) at so_deadlock.cc:9
...
#23 0x0000000000409e69 in ....::_M_complete_async() (this=0xdd4020)
    at /usr/include/c++/4.9/future:1498
#24 0x0000000000402af2 in std::__future_base::_State_baseV2::wait (
    this=0xdd4020) at /usr/include/c++/4.9/future:321
#25 0x0000000000404713 in std::__basic_future<int>::_M_get_result (
    this=0xdd47e0) at /usr/include/c++/4.9/future:621
#26 0x0000000000403c48 in std::future<int>::get (this=0xdd47e0)
    at /usr/include/c++/4.9/future:700
#27 0x000000000040229b in main () at so_deadlock.cc:24

Это только то, что объясняется в других ответах - код в заблокированном разделе (so_deadlock.cc:24) вызывает future :: get (), который, в свою очередь, (принудительнорезультат) попытка снова получить блокировку.

В других случаях это может быть не так просто, обычно есть несколько потоков, но все это есть.

0 голосов
/ 27 мая 2018

Вы блокируете мьютекс и затем ждете одного из фьючерсов, что, в свою очередь, требует блокировки самого мьютекса.Простое правило: не ждите с заблокированными мьютексами.

Кстати: блокировка выходных потоков не очень эффективна, потому что ее можно легко обойти с помощью кода, который вы даже не контролируете.Вместо того, чтобы использовать эти глобальные переменные, создайте поток для кода, который должен что-то выводить (внедрение зависимости), а затем соберите данные из этого потока потокобезопасным способом.Или используйте библиотеку журналов, потому что это, вероятно, то, что вы хотели сделать в любом случае.

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