Форум программистов, компьютерный форум, киберфорум
Loafer
Войти
Регистрация
Восстановить пароль
Оценить эту запись

Concurrency problem

Запись от Loafer размещена 23.03.2020 в 14:40

Асинхронный запуск переданных лямбда-функций в том порядке, в котором они переданы.

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
#include <atomic>
#include <condition_variable>
#include <functional>
#include <iostream>
#include <list>
#include <mutex>
#include <thread>
#include <vector>
 
class Executor
{
public:
  Executor(): _firstExec{true}, _isStop{false}
  {}
 
  ~Executor()
  {
    {
      std::lock_guard<std::mutex> lock(_mutex);
      _isStop = true;
    }
    _cv.notify_one();
 
    if (!_threads.empty())
    {
      _threads.at(0).join();
    }
  }
  
  void Exec(std::function<void()> func)
  {
    {
      std::lock_guard<std::mutex> lock(_mutex);
      _funcs.push_back(func);
    }
    _cv.notify_one();
 
    if (_firstExec)
    {
      _threads.push_back(std::thread(&Executor::Run, this));
      _firstExec = false;
    }
  }
 
private:
  void Run()
  {
    while (!_isStop)
    {
      std::unique_lock<std::mutex> lock(_mutex);
      
      if (!_funcs.empty())
      {
        auto func = _funcs.front();
        _funcs.pop_front();
        lock.unlock();
        func();
      }
      else
      {
        _cv.wait(lock, [this] () { return !_funcs.empty() || _isStop; });
      }
    }
  }
  
  bool _firstExec;
  std::atomic<bool> _isStop;
  
  std::list<std::function<void()>> _funcs;
  std::vector<std::thread> _threads;
 
  std::mutex _mutex;
  std::condition_variable _cv;
};
 
void TestFunc(Executor & executor, int number)
{
  executor.Exec([number]() {
    std::cout << "HI: " << number << std::endl;
  });
}
 
int main()
{
  Executor executor;
  for (int i = 0; i < 6; ++i)
  {
    if (i > 0 && i % 2 == 0)
    {
      std::cout << "Main: HI" << std::endl;
      std::this_thread::sleep_for(std::chrono::seconds{2});
    }
    TestFunc(executor, i + 1);
  }
 
  (void)std::getchar();
  return 0;
}
Размещено в C++
Просмотров 352 Комментарии 20
Всего комментариев 20
Комментарии
  1. Старый комментарий
    Аватар для Avazart
    C++
    1
    2
    3
    4
    5
    6
    7
    
     executor.exec([]() {
        std::cout << "HI 1" << std::endl;
      });
     
      executor.exec([]() {
        std::cout << "HI 2" << std::endl;
      });
    DRY ?
    Запись от Avazart размещена 23.03.2020 в 17:33 Avazart на форуме
  2. Старый комментарий
    Аватар для Loafer
    Надо бы поправить.
    Запись от Loafer размещена 23.03.2020 в 17:46 Loafer вне форума
  3. Старый комментарий
    Аватар для Avazart
    Ну и префиксное подчеркивание в именовании, это "не айс".
    Все же это не python и не C#.
    Запись от Avazart размещена 23.03.2020 в 18:43 Avazart на форуме
  4. Старый комментарий
    Аватар для Avazart
    C++
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      TestFunc(executor, 1);
      TestFunc(executor, 2);
     
      std::cout << "Main: HI" << std::endl;
      std::this_thread::sleep_for(std::chrono::seconds{2});
     
      TestFunc(executor, 3);
      TestFunc(executor, 4);
     
      std::cout << "Main: HI" << std::endl;
     
      TestFunc(executor, 5);
      TestFunc(executor, 6);
    Что изменилось? Тот же copy paste
    Запись от Avazart размещена 23.03.2020 в 18:47 Avazart на форуме
  5. Старый комментарий
    Аватар для Loafer
    Цитата:
    Сообщение от Avazart Просмотреть комментарий
    Ну и префиксное подчеркивание в именовании, это "не айс".
    Ну это уже вкусовщина.
    Запись от Loafer размещена 23.03.2020 в 18:58 Loafer вне форума
  6. Старый комментарий
    Аватар для Avazart
    Когда это стало вкусовщиной в С++ ?
    Есть вроде стандарт который говорит про зарезервированные имена.
    Запись от Avazart размещена 23.03.2020 в 19:08 Avazart на форуме
  7. Старый комментарий
    Аватар для Loafer
    Цитата:
    Сообщение от Avazart Просмотреть комментарий
    Есть вроде стандарт который говорит про зарезервированные имена.
    Был бы рад увидеть такой стандарт.
    Запись от Loafer размещена 23.03.2020 в 19:23 Loafer вне форума
  8. Старый комментарий
    Аватар для Loafer
    Цитата:
    Сообщение от Avazart Просмотреть комментарий
    Что изменилось? Тот же copy paste
    Пофиксил.
    Запись от Loafer размещена 23.03.2020 в 19:29 Loafer вне форума
  9. Старый комментарий
    Аватар для Avazart
    Цитата:
    Был бы рад увидеть такой стандарт.
    7.1.3 Reserved identifiers
    https://stackoverflow.com/questions/...a-c-identifier
    Запись от Avazart размещена 23.03.2020 в 20:09 Avazart на форуме
    Обновил(-а) Avazart 23.03.2020 в 20:13
  10. Старый комментарий
    Аватар для Loafer
    Спасибо.
    Запись от Loafer размещена 23.03.2020 в 20:16 Loafer вне форума
  11. Старый комментарий
    Аватар для gray_fox
    C++
    1
    2
    
    _isStop = true;
     _cv.notify_one();
    C++
    1
    
    _cv.wait(lock, [this] () { return !_funcs.empty() || _isStop; });
    Что бы не нарваться на ситуацию с потерянным уведомлением (lost wakeup) запись в _isStop стоит делать, захватывая тот же мьютекс, который захватывается при проверке предиката в wait.
    Иначе эта запись + notify_one() могут произойти после проверки предиката но до перехода условной переменной в "режим ожидания".
    Запись от gray_fox размещена 23.03.2020 в 22:00 gray_fox вне форума
  12. Старый комментарий
    Аватар для Loafer
    А я же специально обернул _isStop в std::atomic. Это разве не поможет избежать lost wakeup?

    P.S. Все равно не могу понять, как добавление еще одного захвата избавит от ситуации lost wakeup. Можете пояснить на примере?
    Запись от Loafer размещена 23.03.2020 в 23:01 Loafer вне форума
  13. Старый комментарий
    Аватар для gray_fox
    std::atomic позволяет избежать гонки данных в данном случае, но это не критическая секция.
    Запись от gray_fox размещена 23.03.2020 в 23:32 gray_fox вне форума
    Обновил(-а) gray_fox 23.03.2020 в 23:34
  14. Старый комментарий
    Аватар для gray_fox
    Изменение состояния, связанного с условной переменной, и его проверка не должны иметь возможности выполняться параллельно, иначе есть вероятность "потерять" это изменение.
    На том же https://cppreference.com, например, по поводу этого написано следующее:
    Цитата:
    Even if the shared variable is atomic, it must be modified under the mutex in order to correctly publish the modification to the waiting thread.
    Запись от gray_fox размещена 23.03.2020 в 23:34 gray_fox вне форума
  15. Старый комментарий
    Аватар для Loafer
    Спасибо. Пофиксил.
    Запись от Loafer размещена 23.03.2020 в 23:34 Loafer вне форума
  16. Старый комментарий
    Аватар для gray_fox
    Цитата:
    Все равно не могу понять, как добавление еще одного захвата избавит от ситуации lost wakeup.
    При вызове wait разблокировка мьютекса (1) и переход в "спящий режим" (2) производиться атомарно. Если обновление состояния происходит при захваченном мьютексе, то оно произойдёт либо до (1), либо после (2), но никак не между ними.
    Запись от gray_fox размещена 23.03.2020 в 23:58 gray_fox вне форума
  17. Старый комментарий
    Аватар для Loafer
    Просто в вашем предыдущем посте шла речь о состояниях "проверка предиката" и "режим ожидания", а сейчас речь о "разблокировка мьютекса" и "режим ожидания" (он же "спящий режим", как я понимаю).
    Запись от Loafer размещена 24.03.2020 в 00:06 Loafer вне форума
  18. Старый комментарий
    Аватар для gray_fox
    Да, действительно, изложение так себе, извиняюсь) Тут мне привычнее думать в терминах на английском.
    Запись от gray_fox размещена 24.03.2020 в 00:23 gray_fox вне форума
  19. Старый комментарий
    Аватар для Loafer
    Так какой вариант все-таки правильный? Я так понимаю, что второй. Когда речь идет о состояниях "разблокировка мьютекса" и "режим ожидания".
    Запись от Loafer размещена 24.03.2020 в 00:28 Loafer вне форума
  20. Старый комментарий
    Аватар для gray_fox
    Цитата:
    Так какой вариант все-таки правильный? Я так понимаю, что второй. Когда речь идет о состояниях "разблокировка мьютекса" и "режим ожидания".
    Ну они оба правильные, если я правильно понял, о чём вы.
    В начале я написал, что проверка "предиката" (связанного с условной переменной состояния) и его изменение должны происходить с захватом одного и того же мьютекса (я написал именно предикат из за того, что там используется лямбда-выражение при вызове wait).
    Потом на вопрос о том, "как добавление еще одного захвата избавит от ситуации lost wakeup" ответил, почему в таком случае уведомление не может потеряться.
    Запись от gray_fox размещена 24.03.2020 в 22:17 gray_fox вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2020, vBulletin Solutions, Inc.