37 / 35 / 4
Регистрация: 10.01.2017
Сообщений: 1,348
1

Condition_variable - как заставить работать все пробужденные потоки

26.10.2020, 23:37. Показов 1615. Ответов 12
Метки нет (Все метки)

Здравствуйте,

Что-то я на простом примере даже запутался в работе этого 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
int global_int = 0;
mutex my_mutex;
condition_variable my_cond_var;
 
 
int main()
{
 
//---------------------------------
//Создаю два потока:
std::thread my_threads[2];
 
for (int i = 0; i < 2; ++i)
{
       my_threads[i] = std::thread(cout_id, i);
}
//---------------------------------
 
 
global_int = 1;                    //Устанавливаю статус для пробуждения потоков 
my_cond_var.notify_all();    //пробуждаю все потоки
 
 
 
 
for (int i = 0; i < 2; ++i)
    {
       my_threads[i].join();
    }
 
}

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void cout_id(int id)
{
    std::unique_lock<std::mutex> lck(my_mutex, defer_lock);
    
 
 
    while(global_int == 0)
    {
        my_cond_var.wait(lck);
    }
    
 
    std::cout << "thread_start " << id << endl;
    this_thread::sleep_for(chrono::milliseconds(10000));
    std::cout << "thread_end " << id << endl;
}
И работает так, что поток пробуждается выводится:
thread_start:0
thread_end:0
И все, а второй поток - где то висит почему то, ходя вроде бы теоретически тоже должен же сработать. ?
__________________
Помощь в написании контрольных, курсовых и дипломных работ, диссертаций здесь
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
26.10.2020, 23:37
Ответы с готовыми решениями:

Как заставить работать потоки одновременно
Привет,Я запускаю два потока пуля и самолет но они работают в таком порядке сначала пуля летит до...

Как заставить потоки работать одновременно?
Для ядра Linux 2.6.18 или новее написать модуль, который будет делать следующее: 1. Сформировать...

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

Как заставить сайт написанный на 5.3 заставить работать на 5.6? Как принудительно включить register_globals?
Есть древний сайт. Работает на php 5.3. Как объяснил разработчик данного сайта - все дело в...

12
5231 / 2886 / 1207
Регистрация: 07.02.2019
Сообщений: 7,280
26.10.2020, 23:52 2
Optimus11,
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
#include <thread>
#include <mutex>
#include <iostream>
 
int global_int = 0;
std::mutex my_mutex;
std::condition_variable my_cond_var;
 
void cout_id(int id)
{
    std::unique_lock<std::mutex> lck(my_mutex);
    my_cond_var.wait(lck, [] { return global_int != 0; });
 
 
    std::cout << "thread_start " << id << std::endl;
    lck.unlock();
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    lck.lock();
    std::cout << "thread_end " << id << std::endl;
}
 
int main()
{
 
    //---------------------------------
    //Создаю два потока:
    std::thread my_threads[2];
 
    for (int i = 0; i < 2; ++i)
    {
        my_threads[i] = std::thread(cout_id, i);
    }
    //---------------------------------
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    {
        std::lock_guard<std::mutex> lck(my_mutex);
        global_int = 1;                    //Устанавливаю статус для пробуждения потоков
        std::cout << "notify\n";
    }
    my_cond_var.notify_all();    //пробуждаю все потоки
 
 
 
 
    for (int i = 0; i < 2; ++i)
    {
        my_threads[i].join();
    }
 
}
2
С чаем беда...
Эксперт CЭксперт С++
9979 / 5333 / 1459
Регистрация: 18.10.2014
Сообщений: 12,818
27.10.2020, 00:25 3
Цитата Сообщение от Optimus11 Посмотреть сообщение
И все, а второй поток - где то висит почему то, ходя вроде бы теоретически тоже должен же сработать. ?
Типичный race condition. Сколько бы вы не ждали до момента выполнения my_cond_var.notify_all() в main(), все равно нет гарантии, что оба потока уже успели прибыть в точку my_cond_var.wait(lck). И это не вопрос времени.

В том числе специально поэтому conditional variable и используется в паре с mutex. В функции потока делаем

C++
1
    std::unique_lock<std::mutex> lck(my_mutex);
(то есть никакого defer_lock)

А в main() делаем

C++
1
2
3
4
5
  {
    std::unique_lock<std::mutex> lck(my_mutex);
    global_int = 1;
  }
  my_cond_var.notify_all();
Теперь есть гарантия, что к моменту my_cond_var.notify_all() оба потока сидят в my_cond_var.wait(lck).
1
37 / 35 / 4
Регистрация: 10.01.2017
Сообщений: 1,348
27.10.2020, 00:38  [ТС] 4
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
А в main() делаем

C++
1
2
3
4
5
  {
    std::unique_lock<std::mutex> lck(my_mutex);
    global_int = 1;
  }
  my_cond_var.notify_all();
Теперь есть гарантия, что к моменту my_cond_var.notify_all() оба потока сидят в my_cond_var.wait(lck).
Спасибо! Но, что не пойму, а зачем блокировать global_int = 1, если для присвоения global_int = 1 используется только один поток ?
0
5231 / 2886 / 1207
Регистрация: 07.02.2019
Сообщений: 7,280
27.10.2020, 01:45 5
Цитата Сообщение от Optimus11 Посмотреть сообщение
Но, что не пойму, а зачем блокировать global_int = 1, если для присвоения global_int = 1 используется только один поток ?
Причины две:
1) конкурентно читать без синхронизации вы можете только если никто не пишет. Конкурентные запись/чтение должны быть синхронизированы(или атомарны).
2) О чем упомянул TheCalligrapher в своём посте. Даже если сделать global_int атомарным, у вас в коде нет синхронизации последовательностей событий change_condition->notify и check_condition->wait_notify. Суть в том, что событие notify может произойти между событиями check_condition и wait_notify, т.е. check_condition->change_condition->notify->wait_notify а значит уснувший поток не пробудится.
1
С чаем беда...
Эксперт CЭксперт С++
9979 / 5333 / 1459
Регистрация: 18.10.2014
Сообщений: 12,818
27.10.2020, 01:57 6
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Теперь есть гарантия, что к моменту my_cond_var.notify_all() оба потока сидят в my_cond_var.wait(lck).
Здесь я не прав. Разумеется, нет гарантии, что оба потока вообще как-то продвинулись в своем выполнении до того момента, как main захватит mutex. В данном случае это "срабатывает" потому, что созданные потоки успевают захватить mutex до main.
0
5231 / 2886 / 1207
Регистрация: 07.02.2019
Сообщений: 7,280
27.10.2020, 02:34 7
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Здесь я не прав.
В данной конкретной ситуации вы частично правы. Даже если потоки не продвинулись, есть гарантия, что my_cond_var.wait(lck) happens before global_int = 1
0
6565 / 4550 / 1843
Регистрация: 07.05.2019
Сообщений: 13,726
27.10.2020, 13:36 8
Цитата Сообщение от Optimus11 Посмотреть сообщение
Спасибо! Но, что не пойму, а зачем блокировать global_int = 1, если для присвоения global_int = 1 используется только один поток ?
Ты здесь блокируешь не просто чтение global_int, а две операции - чтение global_int и ожидание condition_variable. Без этой блокировки изменение global_int=1 и notify_all могут проскочить между ними, соответственно, всё зависнет

Добавлено через 2 минуты
Ну и по той же причине нужно блокировать global_int = 1 - чтобы проверка и ожидание оба гарантированно выполнились после того как ты присвоишь единицу
1
37 / 35 / 4
Регистрация: 10.01.2017
Сообщений: 1,348
27.10.2020, 16:18  [ТС] 9
Мне кается легче сделать в лоб, чем со всеми этими хитросплетениями Condition_variable разбираться

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main()
{
    std::thread my_threads[500];
 
    for (int i = 0; i < 500; ++i)
    {
        my_threads[i] = std::thread(cout_id, i);
    }
 
 
    global_int = 1;
 
 
 
    for (int i = 0; i < 100; ++i)
    {
        my_threads[i].join();
    }
 
}

C++
1
2
3
4
5
6
7
8
9
10
11
void cout_id(int id)
{
    while (global_int == 0)
    {
        this_thread::sleep_for(chrono::milliseconds(10));
    }
 
    std::cout << "thread_start " << id << endl;
    this_thread::sleep_for(chrono::milliseconds(10000));  //Какая то работа
    std::cout << "thread_end " << id << endl;
}
Конечно это не правильно, но на 500 потоках в этом квази-режиме сна, загрузка процессора увеличивается всего на 4%.
На 100 потоках вообще не заметно разницы в загрузке процессора - между до и после запуска этого кода.
0
6565 / 4550 / 1843
Регистрация: 07.05.2019
Сообщений: 13,726
27.10.2020, 16:59 10
Цитата Сообщение от Optimus11 Посмотреть сообщение
На 100 потоках вообще не заметно разницы в загрузке процессора - между до и после запуска этого кода.
Можно и так, только время отзыва у твоих потоков будет ~10мс, т.е. очень медленно. За это время можно и всю задачу выполнить.
0
Эксперт С++
8719 / 4262 / 950
Регистрация: 15.11.2014
Сообщений: 9,669
27.10.2020, 17:43 11
Цитата Сообщение от zayats80888 Посмотреть сообщение
lck.unlock();

зачем здесь нужен этот unlock?

всмысле, разве my_cond_var не должна его автоматом разблокировать?

Atomically unlocks lock, blocks the current executing thread, and adds it to the list of threads waiting on *this

C++
1
2
3
4
5
    std::unique_lock<std::mutex> lck(my_mutex);
    my_cond_var.wait(lck, [] { return global_int != 0; });  //<--- unlock and wait
 
    std::cout << "thread_start " << id << std::endl;
    lck.unlock();   //<--- already unlocked
однако если сделать "двойной" unlock, получим вылет:

C++
1
2
lck.unlock();
lck.unlock();
Код
terminate called after throwing an instance of 'std::system_error'
terminate called recursively
  what():  Operation not permitted

Abort signal from abort(3) (SIGABRT)
однако в твоём варианте вылетов не наблюдается,
как будто бы lck не был автоматичеки разблокирован...
0
6565 / 4550 / 1843
Регистрация: 07.05.2019
Сообщений: 13,726
27.10.2020, 17:50 12
Цитата Сообщение от hoggy Посмотреть сообщение
всмысле, разве my_cond_var не должна его автоматом разблокировать?
Она его разблокирует только на время ожидания, потом снова блокирует, при выходе из wait. Т.е. вот здесь
Цитата Сообщение от hoggy Посмотреть сообщение
lck.unlock();   //<--- already unlocked
мьютекс будет заблокирован

Добавлено через 1 минуту
1) Atomically unlocks lock, blocks the current executing thread, and adds it to the list of threads waiting on *this. The thread will be unblocked when notify_all() or notify_one() is executed. It may also be unblocked spuriously. When unblocked, regardless of the reason, lock is reacquired and wait exits. If this function exits via exception, lock is also reacquired. (until C++14)
https://en.cppreference.com/w/... iable/wait
1
Эксперт С++
8719 / 4262 / 950
Регистрация: 15.11.2014
Сообщений: 9,669
27.10.2020, 17:57 13
Цитата Сообщение от zayats80888 Посмотреть сообщение
гарантия, что my_cond_var.wait(lck) happens before global_int = 1
выражайся по-русски.

к моменту вызова my_cond_var.wait(lck),
значение переменная global_int может быть равным как нулю,
так и единичке.
1
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
27.10.2020, 17:57
Помогаю со студенческими работами здесь

Как заставить работать ? :)
Form1.label6.caption := FloatTostr (dosp); if Dosp &lt;=0.1 then Form1.label7.caption := '0.1'; ...

как заставить работать QT
Только установила QT. Пытаюсь скомпилировать консольную программу: #include &lt;QTextStream&gt; int...

Как заставить работать do while
Печатает пока 100 строк не будут заполнены, нужно при вводе символа n закончить принятие данных и...

Как заставить это работать
Кароче есть класс он конечно же находится в отдельных hpp и cpp файлах. В классе есть template...

Как заставить работать РС динамик из VB
Как ?

Как заставить работать скрипт
Добрый день! Имеется скрипт проверки совместимости версии iOS для репозитория Cydia. const...


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

Или воспользуйтесь поиском по форуму:
13
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2022, CyberForum.ru