Форум программистов, компьютерный форум, киберфорум
C++
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.61/18: Рейтинг темы: голосов - 18, средняя оценка - 4.61
0 / 0 / 0
Регистрация: 17.07.2020
Сообщений: 19

Синхронизация потоков через condition_variable

17.07.2020, 20:04. Показов 3818. Ответов 17

Студворк — интернет-сервис помощи студентам
Всем доброго времени суток!
Прошу знатоков C++ помочь в решении следующего вопроса:

Имеется некий циклически выполняемый поток, в котором я, используя condition_variable, жду истечения таймаута или завершаю его работу.
Для предотвращения ложных срабатываний - использую глобальную переменную _bStar, которая выставляется в false, когда начинается остановка всей программы:
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
bool _bStart = true;
std::condition_variable _cvThread, _cvThreadEnd;
 
//поток, созданный через std::thread и вызов detach()
void ThreadBuf()
{
  std::mutex mutex;
 
  for(;;)
  {
    std::unique_lock<std::mutex> u_lock(mutex);
    //Жду 200 мс или завершаю цикл, когда выставится _cvThread
    if( _cvThread.wait_for( u_lock, std::chrono::milliseconds(200), []() { return (_bStart == false); } ) )
    {
      break;
    }
 
    //Здесь делаю какую-то работу
    // ….
  }
 
  //Тут делаю еще что-то перед закрытием потока...
 
  //Тут я хочу сигнализировать основному потоку программы, что поток успешно завершен...
  _cvThreadEnd.notify_one();
}
При завершении работы программы я устанавливаю флаг _bStart и сигнализирую потоку через condition_variable о необходимости завершить цикл for(;:
C++
1
2
_bStart = false;
_cvThread.notify_one();
Затем я хочу дождаться завершения потока ThreadBuf:
C++
1
2
std::unique_lock<std::mutex> u_lock(mutex);
_cvThreadEnd.wait_for( u_lock, std::chrono::seconds(10) );
К моему удивлению, _cvThread.wait_for() в потоке, срабатывает еще до вызова: _cvThread.notify_one(), а сразу после установки _bStart = false.
Почему так происходит и как добиться завершения потока только после вызова _cvThread.notify_one()?
Подскажите пожалуйста, знатоки C++!
0
Лучшие ответы (1)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
17.07.2020, 20:04
Ответы с готовыми решениями:

Синхронизация потоков с++
Реализовать модуль создающий 4 балансировочных потока обеспечивающий 100% загрузку CPU (A,B,C,D). Каждый поток должен выводить на консоль...

Не понятно как работает пример std::condition_variable::wait
Здравствуйте! Разбираюсь с std::condition_variable и не могу понять пример кода из cppreference.com. Вот он: #include &lt;iostream&gt;...

Синхронизация потоков через InterlockedCompareExchange
Приветствую. Есть код: #include &lt;iostream&gt; #include &lt;Windows.h&gt; using namespace std; unsigned N = 0, K = 0, size1 = 0; ...

17
 Аватар для zayats80888
6352 / 3523 / 1428
Регистрация: 07.02.2019
Сообщений: 8,995
17.07.2020, 20:13
Иван_79, флаг выставлять нужно под темже mutex'ом, что и проверяешь его в потоке
0
0 / 0 / 0
Регистрация: 17.07.2020
Сообщений: 19
17.07.2020, 20:28  [ТС]
Тоесть: если я выставлю _bStart = false под мьютексом, который использую при вызове:
C++
1
if( _cvThread.wait_for( u_lock, std::chrono::milliseconds(200), []() { return (_bStart == false); } ) )
и при этом не взведу
C++
1
_cvThread.notify_one();
то цикл
C++
1
for(;;)
не завершиться?
0
 Аватар для zayats80888
6352 / 3523 / 1428
Регистрация: 07.02.2019
Сообщений: 8,995
17.07.2020, 20:59
Завершится, почему нет? Без мьютекса просто в коде UB.
0
0 / 0 / 0
Регистрация: 17.07.2020
Сообщений: 19
17.07.2020, 22:09  [ТС]
Зачем тогда нужен вызов condition_variable::notify_one(), если для контроля _cvThread.wait_for() достаточно предиаката?
0
 Аватар для zayats80888
6352 / 3523 / 1428
Регистрация: 07.02.2019
Сообщений: 8,995
17.07.2020, 23:11
Цитата Сообщение от Иван_79 Посмотреть сообщение
Зачем тогда нужен вызов condition_variable::notify_one(), если для контроля _cvThread.wait_for() достаточно предиаката?
У меня встречные вопросы:

Зачем отсоединять поток, который использует общие ресурсы, что бы потом пытаться вручную его прерывать и ждать завершения? Чем не устроил joinable поток?

Зачем вообще вот это
Жду 200 мс или завершаю цикл, когда выставится _cvThread
, чем атомарный флаг не устроил?
1
0 / 0 / 0
Регистрация: 17.07.2020
Сообщений: 19
18.07.2020, 11:21  [ТС]
Цитата Сообщение от zayats80888 Посмотреть сообщение
Зачем отсоединять поток, который использует общие ресурсы, что бы потом пытаться вручную его прерывать и ждать завершения? Чем не устроил joinable поток?
В программе несколько потоков, выполняющихся параллельно, а в случае с join, пришлось бы ждать завершения каждого из них в главном потоке программы.
Цитата Сообщение от zayats80888 Посмотреть сообщение
Зачем вообще вот это
Мне нужно циклически (через каждые 200 мс) выполнять определенную работу, при этом необходимо контролировать момент, когда главный поток сигнализирует нашему о необходимости завершить его работу, также при завершении наш сообщает главному, что он успешно закрылся, а главный должен дождаться этого момента.
Цитата Сообщение от zayats80888 Посмотреть сообщение
чем атомарный флаг не устроил?
Как тут может помочь атомарный флаг?

Прошу прощения за косноязычие.
0
 Аватар для zayats80888
6352 / 3523 / 1428
Регистрация: 07.02.2019
Сообщений: 8,995
18.07.2020, 16:40
Лучший ответ Сообщение было отмечено Иван_79 как решение

Решение

Цитата Сообщение от Иван_79 Посмотреть сообщение
а в случае с join, пришлось бы ждать завершения каждого из них в главном потоке программы.
...
также при завершении наш сообщает главному, что он успешно закрылся, а главный должен дождаться этого момента.
Вы не хотите ждать завершения потока, но при этом хотите ждать завершения потока...

Цитата Сообщение от Иван_79 Посмотреть сообщение
Мне нужно циклически (через каждые 200 мс) выполнять определенную работу, при этом необходимо контролировать момент, когда главный поток сигнализирует нашему о необходимости завершить его работу
У вас работа выполняется с перерывами в 200 мс(если нужно каждые 200 мс - вам нужен таймер).
Вообще для возможности прерывания потока во время ожидания в произвольном примитиве синхронизации придётся написать не очень простую структуру, но в вашем случае 200 мс - это ниочём, можно просто уснуть на этот интервал, проснутся, проверить атомарный флаг.

Добавлено через 19 минут
например что-то в таком роде
Кликните здесь для просмотра всего текста

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
#include <thread>
#include <future>
#include <atomic>
/*****************************************************************************/
// Флаг прерывания, локальный у каждого потока,
// что бы не передавать дополнительные параметры
// и избежать "висячих" ссылок
using interrupt_flag = std::shared_ptr<std::atomic<bool>>;
thread_local interrupt_flag this_thread_interrupt_flag = std::make_shared<std::atomic<bool>>();
 
// Исключение прерывания
class interruption_ex {};
 
// Функция проверки прерывания потока
void interruption_point()
{
    if (this_thread_interrupt_flag->load(std::memory_order_relaxed))
        throw interruption_ex{};
}
/*****************************************************************************/
class interruptable_thread
{
    std::thread m_thread;
    interrupt_flag m_flag;
 
public:
 
    template<class F>
    interruptable_thread(F fn)
    {
        std::promise<interrupt_flag> p;
        m_thread = std::thread([&p, fn]()
        {
            try
            {
                // Передаем разделяемое владение флагом потока
                p.set_value(this_thread_interrupt_flag);
                // и запускаем функцию потока
                fn();
            }
            catch (const interruption_ex&)
            {
                // Обрабатываем прерывание, если нужно
            }
        });
        m_flag = p.get_future().get();
    }
 
    ~interruptable_thread()
    {   
        // В деструкторе прерываем поток и ждём завершения
        interrupt();
        if (m_thread.joinable())
            m_thread.join();
    }
 
    void interrupt() noexcept
    {
        if (m_flag)
            m_flag->store(true, std::memory_order_relaxed);
    }
 
    //...
};
/*****************************************************************************/
void ThreadBuf()
{
    try
    {
        for (;;)
        {
            // Точки прерывания расставляем на своё усмотрение.
            // Для возможности прерывания во время ожидания
            // этот код не подойдёт(он немного усложнится, читай книги)
            std::this_thread::sleep_for(std::chrono::milliseconds(200));
            interruption_point();
            //Здесь делаем какую-то работу
        }
    }
    catch (const interruption_ex&)
    {
        // Если нужно, и тут обрабатываем прерывание
        throw;
    }
    catch (...)
    {
        // Остальное нужно обработать
    }
}
/*****************************************************************************/
int main()
{
    interruptable_thread thr(&ThreadBuf);
    // Поток можно прервать вручную(thr.interrupt())
    // или это произойдёт автоматически в деструкторе
    //...
}
1
0 / 0 / 0
Регистрация: 17.07.2020
Сообщений: 19
19.07.2020, 11:38  [ТС]
Цитата Сообщение от zayats80888 Посмотреть сообщение
Вы не хотите ждать завершения потока, но при этом хотите ждать завершения потока...
Да, я к сожалению, не правильно интерпретировал работу метода join, сейчас разобрался.
Цитата Сообщение от zayats80888 Посмотреть сообщение
если нужно каждые 200 мс - вам нужен таймер
Имеется в виду:
C++
1
std::this_thread::sleep_for() ?
Мне просто до сих пор не понятна работа condition_variable в коде:
C++
1
2
3
4
if( _cvThread.wait_for( u_lock, std::chrono::milliseconds(200), []() { return (_bStart == false); } ) )
{
  break;
}
как тут влияет вызов condition_variable:
C++
1
_cvThread.notify_one();
если все решается состоянием предиката?

ЗЫ: за пример большое спасибо!
0
19491 / 10097 / 2460
Регистрация: 30.01.2014
Сообщений: 17,805
19.07.2020, 13:47
Цитата Сообщение от Иван_79 Посмотреть сообщение
как тут влияет вызов
Этот вызов сразу снимает ожидание, чтобы была возможность проверить предикат.
Либо ожидание снимается по таймауту в 200 миллисекунд.
Поэтому вам и ответили, что предикат все равно можно будет проверить, т.к. есть таймаут.
1
0 / 0 / 0
Регистрация: 17.07.2020
Сообщений: 19
19.07.2020, 14:58  [ТС]
DrOffset
Значит предикат в условной переменной проверяется или по истечению таймаута или при вызове notify_one()/notify_all() и никогда более?
0
19491 / 10097 / 2460
Регистрация: 30.01.2014
Сообщений: 17,805
19.07.2020, 15:02
Иван_79, он проверяется, когда условная переменная выходит из ожидания (сна). А из ожидания она может выйти либо по таймауту (если он был задан), либо по явному запросу на пробуждение через notify_one()/notify_all().
0
20.07.2020, 08:41

Не по теме:

Цитата Сообщение от DrOffset Посмотреть сообщение
А из ожидания она может выйти либо по таймауту (если он был задан), либо по явному запросу на пробуждение через notify_one()/notify_all().
а как же spurious wakeup? :)

0
20.07.2020, 09:17

Не по теме:

Цитата Сообщение от GbaLog- Посмотреть сообщение
а как же spurious wakeup? :)
А ты сам прочитал что по твоей ссылке написано? :)

0
20.07.2020, 09:39

Не по теме:

Цитата Сообщение от DrOffset Посмотреть сообщение
А ты сам прочитал что по твоей ссылке написано? :)
ну я прочитал. но мало что понял. думаю, виной тому моё знание английского(или незнание).
поэтому я полез в книгу "С++. Практика многопоточного программирования" авторства Энтони Уильямса.
и там прочёл вот такое:
В листинге 4.1 используется весьма простая лямбда-функция, проверяющая очередь на пустоту, но ей можно передать любую функцию или вызываемый объект. Если у вас уже есть функция для проверки условия (возможно, проверка сложнее простого тестирования, как здесь), то ее можно передать напрямую — нет необходимости заключать ее в саму лямбда-функцию. В ходе вызова wait() условная переменная может проверять предоставленное условие любое количество раз, но всегда делает это с заблокированным мьютексом и немедленно вернет управление, если и только если предоставленная для тестирования условия функция вернет значение true. Когда ожидающий поток повторно получает блокировку мьютекса и проверяет условие не в ответ на извещение от другого потока, это действие называется ложным пробуждением (spurious wake). Поскольку количество и частота любых ложных пробуждений не регламентируются по определению, задействовать для проверки условия функцию с побочными эффектами не рекомендуется. Если вы нарушите эту рекомендацию, будьте готовы к неоднократному возникновению побочных эффектов.
собственно, это навело на мысль, что из ожидания может выйти ещё и по этому.
если ты под "выходом из ожидания" имел ввиду, что вообще контроль отдаёт вызывающей функции(а я имел ввиду именно пробуждения и проверку предиката), то я неправ.

кстати, там же обнаружил интересное поведение, о котором раньше не думал:
Кликните здесь для просмотра всего текста
Как показано в подразделе 4.1.1, применение цикла тогда, когда используются условные переменные и функции не передается предикат, обусловливается необходимостью обработки ложных пробуждений. Если в цикле воспользоваться функцией wait_for(), ожидание может продлиться почти все отведенное время, прежде чем произойдет ложное пробуждение, после чего при следующем прохождении цикла отсчет времени начнется заново. Это может повторяться произвольное количество раз, аннулируя все границы времени ожидания.

0
20.07.2020, 09:50

Не по теме:

GbaLog-, собственно вопрос не в том, что ложных пробуждений не бывает, а в том из-за чего они происходят.

По ссылке написано, что "ложное пробуждение" не возникает просто так, просто потому что. На многих платформах оно может возникнуть только вследствие notify_one()/notify_all().
Поэтому я и не стал отдельно выделять их в своем сообщении выше.

0
20.07.2020, 10:13

Не по теме:

Цитата Сообщение от DrOffset Посмотреть сообщение
По ссылке написано, что "ложное пробуждение" не возникает просто так, просто потому что. На многих платформах оно может возникнуть только вследствие notify_one()/notify_all().
Поэтому я и не стал отдельно выделять их в своем сообщении выше.
а можешь тогда лучше процитировать? потому что я, вроде, читаю, а вроде и не понимаю.
я, вроде, вижу вот это:
But spurious wakeups don't happen for no reason, they usually happen because in between the time when the condition variable was signaled and when the waiting thread finally ran, another thread ran and changed the condition. There was a race condition between the threads, with the typical result that sometimes, the thread waking up on the condition variable runs first, winning the race, and sometimes it runs second, losing the race.
тут сказано(условно говоря), что notify_one() может вызвать гонку? то есть spurious wakeup может возникать, когда был вызван notify_one(). notify_all() должен пробуждать все потоки, так что это уже не spurious, как я понимаю?

так же в конце статьи написано:
Because spurious wakeups can happen whenever there's a race and possibly even in the absence of a race or a signal, when a thread wakes on a condition variable, it should always check that the condition it sought is satisfied. If it's not, it should go back to sleeping on the condition variable, waiting for another opportunity.
тут же, наоборот, написано, что может произойти даже если гонки и сигнала не было(или тут сигнал имеется ввиду тот, который в процесс приходит. поди разберись).

ещё я залез в ещё одну книжку "The Art of multiprocessor programming".
Like locks, Condition objects must be used in a stylized way. Suppose a thread wants to wait until a certain property holds. The thread tests the property while holding the lock. If the property does not hold, then the thread calls await() to release the lock and sleep until it is awakened by another thread. Here is the key point: there is no guarantee that the property will hold at the time the thread awakens. The await() method can return spuriously (i.e., for no reason), or the thread that signaled the condition may have a wakened too many sleeping threads. Whatever the reason,the thread must retest the property,and if it finds the property still does not hold, it must call await() again.
тут опять говорится об "без причины".

0
20.07.2020, 11:05

Не по теме:

GbaLog-, в общем случае наличие ложных пробуждений зависит от реализации, отсюда все эти, казалось бы, противоречия. "Без причины", это условность, которая обозначает зависимость от реализации. Причина всегда есть.

В той же статье есть пример платформы, на которой пробуждение может произойти из-за системного сигнала.

Цитата Сообщение от GbaLog- Посмотреть сообщение
тут сказано(условно говоря), что notify_one() может вызвать гонку?
Да, условно говоря, это и сказано.

Цитата Сообщение от GbaLog- Посмотреть сообщение
или тут сигнал имеется ввиду тот, который в процесс приходит. поди разберись
Нет, тут речь про сигнал к пробуждению, а не про системный сигнал.

0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
20.07.2020, 11:05
Помогаю со студенческими работами здесь

Синхронизация потоков через мьютексы
Создать и синхронизировать 3 потока с использованием мьютекса

Синхронизация потоков через Event-ы (задача producer/consumer)
Задание :Реализуйте синхронизацию для простейшего случая задачи producer/consumer (spsc – single producer single consumer). Я уже...

Синхронизация потоков
Программа должна работать так: один поток печатает свой id пишет время, спит 20 секунд, потом опять пишет время. После завершения работы...

Синхронизация потоков
Есть класс class CFrameBufferObserver : public VsCoreLib::IFrameBufferObserver { VsCoreLib::IFrameBuffer* m_frame_buffer; ...

Синхронизация потоков в c++
Совершенно не понятно что не так и как правильно. Задача: Отсортировать массив целых чисел. Программу разбить на два синхронизированных...


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

Или воспользуйтесь поиском по форуму:
18
Ответ Создать тему
Новые блоги и статьи
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост.
Programma_Boinc 28.12.2025
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост. Налог на собак: https:/ / **********/ gallery/ V06K53e Финансовый отчет в Excel: https:/ / **********/ gallery/ bKBkQFf Пост отсюда. . .
Кто-нибудь знает, где можно бесплатно получить настольный компьютер или ноутбук? США.
Programma_Boinc 26.12.2025
Нашел на реддите интересную статью под названием Anyone know where to get a free Desktop or Laptop? Ниже её машинный перевод. После долгих разбирательств я наконец-то вернула себе. . .
Thinkpad X220 Tablet — это лучший бюджетный ноутбук для учёбы, точка.
Programma_Boinc 23.12.2025
Рецензия / Мнение/ Перевод Нашел на реддите интересную статью под названием The Thinkpad X220 Tablet is the best budget school laptop period . Ниже её машинный перевод. Thinkpad X220 Tablet —. . .
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Как объединить две одинаковые БД Access с разными данными
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru