Тупик с использованием std :: mutex для защиты cout в нескольких потоках

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

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

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

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

Когда функция печати остается или заканчивается одна итерация цикла 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)

Получить результат f.get(); будущего в переменной перед защитой блокировки

Killzone Kid 27.05.2018 12:19

std :: cout является потокобезопасным, поэтому его можно использовать без блокировок и при этом получать ожидаемый результат: stackoverflow.com/a/15034536/985296

stefan 27.05.2018 13:00

В связи с этим, ostringstream, вероятно, не страдает этими проблемами. То есть ostringstream oss; oss << "Done thread" << f.get() << "(" << this_thread::get_id() << ")" << endl; cout << oss.str(); Также смотрите Cout синхронизирован / потокобезопасен?

jww 27.05.2018 13:32

Спасибо за ценные ответы. Ошибка заключалась в использовании f.get () при заблокированном мьютексе. Также отлично работает версия ostringstream.

frankk 27.05.2018 13:33
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
12
4
1 433
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

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

Ответ принят как подходящий

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

Returns the value stored in the shared state (or throws its exception) when the shared state is ready.

If the shared state is not yet ready (i.e., the provider has not yet set its value or exception), the function blocks the calling thread and waits until it is ready.

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;

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

Я скомпилировал и запустил ваш пример, а затем после присоединения к нему с помощью 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 (), который, в свою очередь (принудительно вызывая результат), пытается снова получить блокировку.

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

Другие вопросы по теме