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

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

17.07.2020, 20:04. Показов 3958. Ответов 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
19500 / 10105 / 2461
Регистрация: 30.01.2014
Сообщений: 17,818
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
19500 / 10105 / 2461
Регистрация: 30.01.2014
Сообщений: 17,818
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
Ответ Создать тему
Новые блоги и статьи
Вывод данных через динамический список в справочнике
Maks 01.04.2026
Реализация из решения ниже выполнена на примере нетипового справочника "Спецтехника" разработанного в конфигурации КА2. Задача: вывести данные из ТЧ нетипового документа. . .
Функция заполнения текстового поля в реквизите формы документа
Maks 01.04.2026
Алгоритм из решения ниже реализован на нетиповом документе "ВыдачаОборудованияНаСпецтехнику" разработанного в конфигурации КА2, в дополнении к предыдущему решению. На форме документа создается. . .
К слову об оптимизации
kumehtar 01.04.2026
Вспоминаю начало 2000-х, университет, когда я писал на Delphi. Тогда среди программистов на форумах активно обсуждали аккуратную работу с памятью: нужно было следить за переменными, вовремя. . .
Идея фильтра интернета (сервер = слой+фильтр).
Hrethgir 01.04.2026
Суть идеи заключается в том, чтобы запустить свой сервер, о чём я если честно мечтал давно и давно приобрёл книгу как это сделать. Но не было причин его запускать. Очумелые учёные напечатали на. . .
Модель здравосоХранения 6. ESG-повестка и устойчивое развитие; углублённый анализ кадрового бренда
anaschu 31.03.2026
В прикрепленном документе раздумья о том, как можно поменять модель в будущем
10 пpимет, которые всегда сбываются
Maks 31.03.2026
1. Чтобы, наконец, пришла маршрутка, надо закурить. Если сигарета последняя, маршрутка придет еще до второй затяжки даже вопреки расписанию. 2. Нaдоели зима и снег? Не надо переезжать. Достаточно. . .
Перемещение выделенных строк ТЧ из одного документа в другой
Maks 31.03.2026
Реализация из решения ниже выполнена на примере нетипового документа "ВыдачаОборудованияНаСпецтехнику" с единственной табличной частью "ОборудованиеИКомплектующие" разработанного в конфигурации КА2. . . .
Functional First Web Framework Suave
DevAlt 30.03.2026
Sauve. IO Апнулись до NET10. Из зависимостей один пакет, работает одинаково хорошо как в режиме проекта так и в интерактивном режиме. из сложностей - чисто функциональный подход. Решил. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru