## 42-Оповещения о событиях
## happen-before
Тут буквально полный ответ на вопрос
https://github.com/hse-spb-2021-cpp/lectures/blob/961bda076612a58f17ba4f62e506fb281e04e7ec/19-220214/01-model/04-happens-before.md
## Producer-consumer и cond_var
Это некоторая модель, где есть *producer* - который отдаёт данные, и *consumer* - который их должен в онлайн режиме читать и обрабатывать.
Есть несколько реализаций (код Егора):
1. **Promise and future:**
```C++
#include <chrono>
#include <future>
#include <iostream>
#include <string>
#include <thread>
int main() {
std::promise<std::string> input_promise;
std::future<std::string> input_future = input_promise.get_future();
std::thread producer([&]() {
std::string input;
std::cin >> input;
input_promise.set_value(std::move(input));
});
std::thread consumer([&]() {
std::cout << "Consumer started\n";
std::string input = input_future.get();
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
std::cout << "Got string: " << input << "\n";
});
consumer.join();
producer.join();
}
```
Здесь можно увидеть, что *producer* читает данные с ввода и передаёт их в специальную переменную *promise* (коробочку), из которой *consumer* с помощью метода `get()` у переменной типа *future*, который не просто считывает содержимое *promise*, а ждёт, пока туда запишут какое-то значение.
Заметим, что *future* констрактится от *promise* и на самом деле, такая конструкция не способна передавать очередь значений, она лишь работает на "единичных передачках".
2. **Async (так писать не рекомендуется):**
```C++
std::future<std::string> input_future = std::async([]() {
std::string input;
std::cin >> input;
return input;
});
```
Это другая реализация *producer*, остальной код такой же.
*async* - по факту "прячет" в себя *thread* и *promise*. То есть мы таким образом говорим, что эта переменная вычислится в соседнем потоке когда-нибудь.
Это плохая практика, так как *async* - мы соверешенно не контроллируем, не умеем отключать поток(если он нам стал не нужен) например.
3. **Mutex-ы (так писать не рекомендуется):**
```C++
std::mutex m;
std::string input;
bool input_available = false;
std::thread producer([&]() {
while (true) {
std::cin >> input;
std::unique_lock l(m);
input_available = true;
}
});
std::thread consumer([&]() {
while (true) {
std::string input_snapshot;
{
std::unique_lock l(m);
if (!input_available) {
continue;
}
input_snapshot = input; // Не хотим, чтобы input изменился, пока мы ждём две секунды.
input_available = false;
} // l.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
std::cout << "Got string: " << input_snapshot << "\n";
}
});
consumer.join();
producer.join();
}
```
Идея таже, но вот в цикле *while* у *consumer* постоянно берётся *mutex* - это сильно жрёт проц + бесполезно, так как на самом деле мы хотим его взять, только когда *producer* нам что-нибудь отправит, чтобы это исправить есть 4-ый способ.
4. **Cond_var** (the coolest):
Что такое *condition_variable*? Это такая переменная которая обладает методами: *.wait(m)* - ждёт, пока от этой же переменной вызовут *notify* на текущий поток, отпускает *mutex* ( *m* ) пока ждёт
*.notify* - будит произвольный поток (может всех, может одного) в котором запущен *wait* на этой переменной.
Таким образом, она позволяет усыплять поток и будить его другим потоком.
```C++
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
int main() {
std::mutex m;
std::string input;
bool input_available = false;
std::condition_variable cond;
std::thread producer([&]() {
while (true) {
std::cin >> input;
std::unique_lock l(m);
input_available = true;
cond.notify_one(); // Разбудит один случайный поток. Важно, чтобы это было ему по делу.
}
});
std::thread consumer([&]() {
while (true) {
std::unique_lock l(m);
while (!input_available) { // while, не if!
cond.wait(l);
}
// Эквивалентная while конструкция, чтобы было сложнее набагать и были понятнее намерения.
// cond.wait(l, []() { return input_available; }); // "ждём выполнения предиката".
std::string input_snapshot = input;
input_available = false;
l.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
std::cout << "Got string: " << input_snapshot << "\n";
}
});
consumer.join();
producer.join();
}
```
Тут после того, как *producer* считал данные и вызыватся *notify_one* у *cond_var*-а и таким образом запускается один случайный поток с *consumer*-ом.
Важно заметить, что `cond.wait(l)` происходит внутри `while` а не `if`, т.к. существует `spurious wakeup`, когда система может просто так взять и разбудить поток, в таком случае мы бы вышли из `if` и пошли бы дальше, хотя *input* ещё не поступал.
## Mutable
Это ключевое слово нужно, когда мы хотим менять какую-то переменную в *const* методах класса, в частности, это бывает полезно для *mutex*:
```C++
#include <iostream>
#include <mutex>
struct user {
mutable std::mutex m_m;
int m_balance = 1000;
int balance_up() const {
std::unique_lock l(m_m);
return m_balance;
}
void get_salary(){
std::unique_lock l(m_m);
m_balance+=30;
}
};
```
В данном коде, функция `balnce()` - потокобезопасная и с *const* квалифаером, если бы *mutex* был не *mutable* - мы бы не могли так сделать.
## Links:
https://github.com/hse-spb-2021-cpp/exercises/tree/master/18-220207/solution - практика на эту тему
https://github.com/hse-spb-2021-cpp/lectures/tree/master/18-220207 - лекция на эту тему.