Одной из структур данных, часто применяемых для вывода данных, является std::cout
cout
на конкурентной основе, то мы получим смешанные выходные данные. Чтобы это предотвратить, следует написать собственную функцию, которая выводит данные на экран и защищена от конкурентности.Мы узнаем, как предоставить оболочку для cout
cout
.Как это делается
В этом примере мы реализуем программу, выводящую на экран данные на конкурентной основе из нескольких потоков. Чтобы предотвратить искажение сообщений из-за конкурентности, реализуем небольшой вспомогательный класс, который синхронизирует вывод данных между потоками.
1. Как и обычно, сначала укажем все директивы include
std
:#include
#include
#include
#include
#include
using namespace std;
2. Далее реализуем вспомогательный класс, который назовем pcout
pcout
явно наследует от stringstream
. Таким образом, можно применять operator<<
для экземпляров этого класса. Как только экземпляр pcout
уничтожается, его деструктор блокирует мьютекс, а затем выводит на экран содержимое буфера stringstream
. На следующем шаге мы увидим, как это использовать.struct pcout : public stringstream {
static inline mutex cout_mutex;
~pcout() {
lock_guard
cout << rdbuf();
cout.flush();
}
};
3. Теперь напишем две функции, которые можно выполнить с помощью дополнительных потоков. Обе функции принимают в качестве аргументов идентификаторы потоков. Затем они отличаются только тем, что одна из них использует для вывода данных непосредственно cout
cout
создает экземпляр pcout
. Этот экземпляр представляет собой временный объект, существующий только для одной строки кода. После выполнения всех вызовов operator<<
внутренний строковый поток заполняется всеми данными, которые мы бы хотели вывести. Затем вызывается деструктор для экземпляра типа pcout
. Мы уже видели, что он делает: блокирует конкретный мьютекс, разделяемый всеми экземплярами класса pcout
, и выводит данные на экран:static void print_cout(int id)
{
cout << "cout hello from " << id << '\n';
}
static void print_pcout(int id)
{
pcout{}
}
4. Опробуем его. Сначала воспользуемся print_cout
cout
. Запускаем десять потоков, конкурентно выводящих свои строки и ожидающих завершения:int main()
{
vector
for (size_t i {0}; i < 10; ++i) {
v.emplace_back(print_cout, i);
}
for (auto &t : v) { t.join(); }
5. Далее делаем то же самое для функции print_pcout
cout << "=====================\n";
v.clear();
for (size_t i {0}; i < 10; ++i) {
v.emplace_back(print_pcout, i);
}
for (auto &t : v) { t.join(); }
}
6. Компиляция и запуск программы дадут следующий результат (рис. 9.3). Как видите, первые десять строк полностью перепутались. Именно так может выглядеть результат, когда cout
print_pcout
, никакой путаницы нет. Можно увидеть, что они выводятся из разных потоков, поскольку их порядок различается при каждом запуске программы.Как это работает
О’кей, мы создали эту
Выполним те же действия, что и pcout
stringstream ss;
ss << "This is some printed line " << 123 << '\n';
Затем блокируем глобально доступный мьютекс:
{
lock_guard