Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
WLF
1 / 2 / 0
Регистрация: 06.12.2013
Сообщений: 118

Многопоточность. Condition_variable. Как проверить одновременно две переменных?

30.07.2025, 15:40. Показов 2021. Ответов 12

Студворк — интернет-сервис помощи студентам
Всем привет.
Классическая задача Читатели-Писатели. Два потока пишут в очередь, один поток читает эту очередь.
В данном виде, при нажатии на ESC программа прерывает оба потока-писателя, как и задумано.
Но проблема в том, что поток-читатель спит в cv.wait() и, соответственно, не знает об изменении переменной exitFlag.
Как-то можно одновременно проверить две переменные?
Дополнительная проверка в 126 строке не помогает. Несколько последовательных запусков даёт разные результаты: Поток-читатель либо продолжает спать, либо при выходе и программы очередь остаётся не пустой.
Или в данном случае придётся отказаться от condition_variable и, так-же как и в потоках-писателях использовать вечный цикл?

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// test_Threads2.cpp
//
#include <iostream>
#include <string>
#include <windows.h>
#include <queue>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <random>
 
#define NUMBER1 42
#define NUMBER2 35
 
 
std::random_device rd;
std::queue<std::wstring> que;
std::mutex mtx, g_lockprint;
std::condition_variable cv;
std::atomic_bool startFlag = false, exitFlag = false, dataReady = false;
 
int GetRandom(int m, int n) {
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> distrib(m, n);
    return distrib(gen);
}
 
// Имитация работы потока. Поток должен работать до завершения программы.
// При совпадении условия генерируется строка, блокируется мютекс, строка помещается в очередь.
// После чего устанавливается флаг готовности данных и подётся сигнал потоку-Читателю.
void Writer1() {
    // Стартовое сообщение. Подсмотрел на Хабре.
    {
        std::unique_lock<std::mutex> locker(g_lockprint);
        std::cout << "Writer-1 running..." << std::endl;
    }
 
    // Тоже на Хабре подсмотрел.
    // Старт всех потоков по одному сигналу.
    while (!startFlag.load()) std::this_thread::yield;
 
    int r = 0; uint64_t i = 0; std::wstring s;
 
    while (false == exitFlag.load()) {
        r = GetRandom(0, 1000);
        //std::wcout << L"i: " << i << L"\tr = " << r << L"                            \r";
        if (r == NUMBER1) {
            s = L"Number " + std::to_wstring(NUMBER1) + L": iteration #" + std::to_wstring(i);
            {
                std::unique_lock<std::mutex> locker(mtx);
                que.push(s);
                dataReady = true;
            }
            cv.notify_one();
        }
        ++i;
    }
 
    // Сообщение о завершении
    {
        std::unique_lock<std::mutex> locker(g_lockprint);
        std::cout << "Writer-1 stopped." << std::endl;
    }
}
 
//// Имитация работы потока аналогично первому.
void Writer2() {
    // Стартовое сообщение. Подсмотрел на Хабре.
    {
        std::unique_lock<std::mutex> locker(g_lockprint);
        std::cout << "Writer-2 running..." << std::endl;
    }
 
    // Тоже на Хабре подсмотрел.
    // Старт всех потоков по одному сигналу.
    while (!startFlag.load()) std::this_thread::yield;
 
    int r = 0; uint64_t i = 0; std::wstring s;
 
    while (false == exitFlag.load()) {
        r = GetRandom(0, 1000);
        //std::wcout << L"i: " << i << L"\tr = " << r << L"                            \r";
        if (r == NUMBER2) {
            s = L"Number " + std::to_wstring(NUMBER2) + L": iteration #" + std::to_wstring(i);
            {
                std::unique_lock<std::mutex> locker(mtx);
                que.push(s);
                dataReady = true;
            }
            cv.notify_one();
        }
        ++i;
    }
 
    // Сообщение о завершении
    {
        std::unique_lock<std::mutex> locker(g_lockprint);
        std::cout << "Writer-2 stopped." << std::endl;
    }
}
 
// Поток-читатель. Должен работать до окончания работы программы.
// При получении сигнала от Писателя должен заблокировать мютекс,
// вывести на экран строки из очереди, удалить эти строки из очереди
// и разблокировать мютекс.
void Reader() {
    // Стартовое сообщение
    {
        std::unique_lock<std::mutex> locker(g_lockprint);
        std::cout << "Reader running..." << std::endl;
    }
    while (!startFlag.load()) std::this_thread::yield;
 
    while (false == exitFlag.load()) {
        {
            std::unique_lock<std::mutex> locker(mtx);
            cv.wait(locker, []() { return dataReady.load(); });
            if (!que.empty()) {
                for (; !que.empty(); que.pop()) {
                    std::wcout << que.front() << std::endl;
                }
            }
            dataReady = false;
        }
        if (true == exitFlag.load()) break; // Добавление этой проверки не помогает.
    }
 
    // Сообщение о завершении
    {
        std::unique_lock<std::mutex> locker(g_lockprint);
        std::cout << "Reader stopped." << std::endl;
    }
}
 
int main() {
    setlocale(LC_ALL, ".UTF8");
    SetConsoleOutputCP(CP_UTF8);
    std::wcout << L"Start main thread" << std::endl;
    std::wcout << L"Press ESC to exit or CTRL+C to break" << std::endl;
 
    exitFlag = false;
    dataReady = false;
 
    std::thread t0(Reader);
    std::thread t1(Writer1);
    std::thread t2(Writer2);
 
    startFlag = true;
 
    // Имитация бесконечной работы основного потока, до прерывания пользователем.
    while (!(GetAsyncKeyState(VK_ESCAPE) & 0x8000)) {}
 
    // При нажатии на ESC устанавливается флаг для остановки всех остальных потоков и подётся сигнал.
    exitFlag = true;
    cv.notify_one();
 
    t1.join();
    t2.join();
    t0.join();
 
    std::wcout << L"\nQueue size: " << que.size() << std::endl;
 
    std::wcout << L"End main thread" << std::endl;
    return 0;
}
0
Лучшие ответы (1)
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
30.07.2025, 15:40
Ответы с готовыми решениями:

Многопоточность. Не работает condition_variable
Доброго времени суток! Есть задание: Пользователь вводит строку с клавиатуры. Первой поток должен...

Не понятно как работает пример std::condition_variable::wait
Здравствуйте! Разбираюсь с std::condition_variable и не могу понять пример кода из...

Condition_variable - как заставить работать все пробужденные потоки
Здравствуйте, Что-то я на простом примере даже запутался в работе этого Condition_variable: ...

12
1968 / 824 / 115
Регистрация: 01.10.2012
Сообщений: 4,859
Записей в блоге: 2
30.07.2025, 17:38
1. cv.notify_one() у Вас выполняется НЕ "под мутексом". Даже если это не ошибка - то ненужный поиск приключений

2. Когда писатель получает мутекс нужно проверить exitFlag и выйти если он установлен (за время ожидания)

3. Конечно Вы имеете право на свой стиль, но вот такое
C++
1
(false == exitFlag.load())
Отпугивает окружающих
0
 Аватар для zayats80888
6352 / 3523 / 1428
Регистрация: 07.02.2019
Сообщений: 8,995
30.07.2025, 17:46
Цитата Сообщение от WLF Посмотреть сообщение
Как-то можно одновременно проверить две переменные?
Дополнительная проверка в 126 строке не помогает.
А почему же эта проверка в 126 строке, а не там же, где и первая?

Не по теме:

Цитата Сообщение от Igor3D Посмотреть сообщение
Даже если это не ошибка - то ненужный поиск приключений
Это не ошибка.
Под приключениями имеются ввиду другие ошибки? Почему же их обнаружение стало нежелательным?

0
1968 / 824 / 115
Регистрация: 01.10.2012
Сообщений: 4,859
Записей в блоге: 2
30.07.2025, 20:46
Цитата Сообщение от Igor3D Посмотреть сообщение
2. Когда писатель получает мутекс нужно проверить exitFlag и выйти если он установлен (за время ожидания)
Не совсем. Когда писатель захватил мутекс и exitFlag = true, читатель может
а) уже все сделать и выйти
б) ждать на cv

Получается так (писатель)
C++
1
2
3
4
5
6
7
{
   std::unique_lock<std::mutex> locker(mtx);
   if (!exitFlag.load())
     queue.push(s);
   dataReady = true;
}
cv.notify_one();
Выходит dataReady совсем не избыточно (как кажется на первый взгляд)
0
30.07.2025, 21:06

Не по теме:

Цитата Сообщение от Igor3D Посмотреть сообщение
Выходит dataReady совсем не избыточно (как кажется на первый взгляд)
Да не нужен этот флаг в этом коде.

0
1968 / 824 / 115
Регистрация: 01.10.2012
Сообщений: 4,859
Записей в блоге: 2
30.07.2025, 22:21
Лучший ответ Сообщение было отмечено WLF как решение

Решение

Цитата Сообщение от zayats80888 Посмотреть сообщение
Да не нужен этот флаг в этом коде.
Да, можно так
C++
1
cv.wait(locker, []() { return !queue.empty() || exitFlag.load(); });
1
WLF
1 / 2 / 0
Регистрация: 06.12.2013
Сообщений: 118
31.07.2025, 12:52  [ТС]
Цитата Сообщение от zayats80888 Посмотреть сообщение
А почему же эта проверка в 126 строке, а не там же, где и первая?
Для того, чтобы если вдруг очередь в этот момент не пустая, вывести её содержимое.

Цитата Сообщение от Igor3D Посмотреть сообщение
Да, можно так
Спасибо, эта конструкция помогла.
0
1968 / 824 / 115
Регистрация: 01.10.2012
Сообщений: 4,859
Записей в блоге: 2
31.07.2025, 16:16
А нет ли тут еще помарки?
C++
1
while (false == exitFlag.load()) {
Допустим читатель увидел exitFlag (установленный главной ниткой) и вышел вон. Не вижу почему очередь должна быть пуста. Писатель тоже не может просто так выйти - некому будет разбудить ожидающего читателя. Или мне что-то померещилось?
0
1458 / 474 / 70
Регистрация: 22.09.2023
Сообщений: 1,438
01.08.2025, 00:47
В код не вникал, но по моим понятиям один из писателей, обнаружив ESC, должен положить в очередь некую команду читателю "баста карапузики, кончилися танцы" и дождаться, пока читатель разгребет очередь, доберется до этой команды и завершится.
0
WLF
1 / 2 / 0
Регистрация: 06.12.2013
Сообщений: 118
01.08.2025, 10:52  [ТС]
Цитата Сообщение от Igor3D Посмотреть сообщение
Допустим читатель увидел exitFlag (установленный главной ниткой) и вышел вон. Не вижу почему очередь должна быть пуста. Писатель тоже не может просто так выйти - некому будет разбудить ожидающего читателя. Или мне что-то померещилось?
У писателя вечный цикл без ожидания. Он выйдет в любом случае, когда главный поток установит exitFlag.
А вот с читателем - да, возникают проблемы. При многократном последовательном запуске программы очередь иногда остаётся не пустой при завершении главного потока. Я как раз сейчас пытаюсь это решить. Хотелось бы, чтобы читатель завершал свою работу гарантированно после завершения работы писателей.

Добавлено через 1 минуту
Цитата Сообщение от Dushevny Посмотреть сообщение
В код не вникал, но по моим понятиям один из писателей, обнаружив ESC, должен положить в очередь некую команду читателю "баста карапузики, кончилися танцы" и дождаться, пока читатель разгребет очередь, доберется до этой команды и завершится.
А зачем писателю ждать читателя?
0
1968 / 824 / 115
Регистрация: 01.10.2012
Сообщений: 4,859
Записей в блоге: 2
01.08.2025, 12:13
Писатель
C++
1
2
3
4
5
6
7
8
9
10
11
bool done = false;
while (!done) {
//..
 {
   std::unique_lock<std::mutex> locker(mtx);
   done = exitFlag.load();
   if (!done)
    queue.push(s);
  }
  cv.notify_one();
}
Читатель
C++
1
2
3
4
5
6
7
8
bool done = false;
while (!done) {
    std::unique_lock<std::mutex> locker(mtx);
    cv.wait(locker, []() { return !queue.empty() || exitFlag.load(); });
    done = exitFlag.load();
    for (; !queue.empty(); queue.pop()) 
      std::wcout << queue.back() << std::endl;
}
done - локальная и даже не атомик, но она считывается под мутексом. Поэтому while (!done) - корректный выход, гарантируется что писатель сделал побудку, а читатель впарил очередь
1
1458 / 474 / 70
Регистрация: 22.09.2023
Сообщений: 1,438
01.08.2025, 20:09
Цитата Сообщение от WLF Посмотреть сообщение
А зачем писателю ждать читателя?
Чтобы корректно завершить программу, не прибив читателя посреди его работы?
0
1968 / 824 / 115
Регистрация: 01.10.2012
Сообщений: 4,859
Записей в блоге: 2
02.08.2025, 00:14
Цитата Сообщение от Dushevny Посмотреть сообщение
по моим понятиям один из писателей, обнаружив ESC, должен положить в очередь некую команду читателю "баста карапузики, кончилися танцы" и дождаться, пока читатель разгребет очередь, доберется до этой команды и завершится.
Это другой, более высокоуровневый подход, напр используя QThread, QEventLoop и др средства фреймворка Qt нитки могут комфортно обмениваться событиями. Но здесь об этом речь не идет, это тема др разговора
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
02.08.2025, 00:14
Помогаю со студенческими работами здесь

Как устроен condition_variable::wait?
Всем привет, изучаю c_v стандартной библиотеки и возник вопрос что происходит когда...

Использование boost::condition_variable
Вот пример из документации: boost::condition_variable cond; boost::mutex mut; bool data_ready;...

C++11, потоки, std::condition_variable
Проблема в том, что в коде ниже сначала работает лишь поток th1, а затем только th2 (поток th1...

Синхронизация потоков через condition_variable
Всем доброго времени суток! Прошу знатоков C++ помочь в решении следующего вопроса: Имеется...

std::condition_variable . для выделенных потоков
Здравствуйте! Попробовал модно реализовать переключение потоков в активный режим с...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
13
Ответ Создать тему
Новые блоги и статьи
SDL3 для Web (WebAssembly): Обработчик клика мыши в браузере ПК и касания экрана в браузере на мобильном устройстве
8Observer8 02.02.2026
Содержание блога Для начала пошагово создадим рабочий пример для подготовки к экспериментам в браузере ПК и в браузере мобильного устройства. Потом напишем обработчик клика мыши и обработчик. . .
Философия технологии
iceja 01.02.2026
На мой взгляд у человека в технических проектах остается роль генерального директора. Все остальное нейронки делают уже лучше человека. Они не могут нести предпринимательские риски, не могут. . .
SDL3 для Web (WebAssembly): Вывод текста со шрифтом TTF с помощью SDL3_ttf
8Observer8 01.02.2026
Содержание блога В этой пошаговой инструкции создадим с нуля веб-приложение, которое выводит текст в окне браузера. Запустим на Android на локальном сервере. Загрузим Release на бесплатный. . .
SDL3 для Web (WebAssembly): Сборка C/C++ проекта из консоли
8Observer8 30.01.2026
Содержание блога Если вы откроете примеры для начинающих на официальном репозитории SDL3 в папке: examples, то вы увидите, что все примеры используют следующие четыре обязательные функции, а. . .
SDL3 для Web (WebAssembly): Установка Emscripten SDK (emsdk) и CMake для сборки C и C++ приложений в Wasm
8Observer8 30.01.2026
Содержание блога Для того чтобы скачать Emscripten SDK (emsdk) необходимо сначало скачать и уставить Git: Install for Windows. Следуйте стандартной процедуре установки Git через установщик. . . .
SDL3 для Android: Подключение Box2D v3, физика и отрисовка коллайдеров
8Observer8 29.01.2026
Содержание блога Box2D - это библиотека для 2D физики для анимаций и игр. С её помощью можно определять были ли коллизии между конкретными объектами. Версия v3 была полностью переписана на Си, в. . .
Инструменты COM: Сохранение данный из VARIANT в файл и загрузка из файла в VARIANT
bedvit 28.01.2026
Сохранение базовых типов COM и массивов (одномерных или двухмерных) любой вложенности (деревья) в файл, с возможностью выбора алгоритмов сжатия и шифрования. Часть библиотеки BedvitCOM Использованы. . .
SDL3 для Android: Загрузка PNG с альфа-каналом с помощью SDL_LoadPNG (без SDL3_image)
8Observer8 28.01.2026
Содержание блога SDL3 имеет собственные средства для загрузки и отображения PNG-файлов с альфа-каналом и базовой работы с ними. В этой инструкции используется функция SDL_LoadPNG(), которая. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru