Выполнение начинается в функции-члене run()
(5), которая устанавливает начальное состояние waiting_for_card
(6), а затем в цикле вызывает функции-члены, представляющие текущее состояние (каким бы оно ни было) (7). Функции состояния — это просто функции-члены класса atm
. Функция waiting_for_card
(1) тоже не представляет сложности: она посылает сообщение интерфейсу с просьбой вывести сообщение «Вставьте карту» (2), а затем ожидает сообщения, которое могла бы обработать (3). Единственное допустимое в этой точке сообщение — card_inserted
; оно обрабатывается лямбда-функцией (4). Функции handle
можно передать любую функцию или объект-функцию, но в таком простом случае лямбда-функции вполне достаточно. Отметим, что вызов функции handle()
сцеплен с вызовом wait()
; если получено сообщение недопустимого типа, оно отбрасывается, и поток ждет, пока не придёт подходящее сообщение.
Сама лямбда-функция просто запоминает номер карточного счета в переменной-члене, очищает текущий ПИН-код и переходит в состояние «получение ПИН». По завершении обработчика сообщений функция состояния возвращает управление главному циклу, который вызывает функцию следующего состояния (7).
Функция состояния getting_pin
несколько сложнее, потому что может обрабатывать сообщения разных типов, как следует из рис. 4.3. Ниже приведён ее код.
Листинг 4.16. Функция состояния getting_pin
для простой реализации банкомата
void atm::getting_pin() {
incoming.wait()
.handle
(1)
[&](digit_pressed const& msg) {
unsigned const pin_length = 4;
pin += msg.digit;
if (pin.length() == pin_length) {
bank.send(verify_pin(account, pin, incoming));
state = &atm::verifying_pin;
}
}
)
.handle
(2)
[&](clear_last_pressed const& msg) {
if (!pin.empty()) {
pin.resize(pin.length() - 1);
}
}
)
.handle
(3)
[&](cancel_pressed const& msg) {
state = &atm::done_processing;
}
);
}
Поскольку теперь допустимы сообщения трех типов, то с функцией wait()
сцеплены три вызова функции handle()
(1), (2), (3). В каждом вызове handle()
в качестве параметра шаблона указан тип сообщения, а в качестве параметра самой функции — лямбда-функция, которая принимает сообщение этого типа. Поскольку вызовы сцеплены, функция wait()
знает, что может ожидать сообщений digit_pressed
, clear_last_pressed
или cancel_pressed
. Сообщения всех прочих типов игнорируются.
Как видим, теперь состояние изменяется не всегда. Например, при получении сообщения digit_pressed
мы просто дописываем цифру в конец pin
, если эта цифра не последняя. Затем главный цикл ((7) в листинге 4.15) снова вызовет функцию getting_pin()
, чтобы ждать следующую цифру (или команду очистки либо отмены).
Это соответствует поведению, изображенному на рис. 4.3. Каждое состояние реализовано отдельной функцией-членом, которая ждет сообщений определенных типов и при необходимости обновляет состояние.
Как видите, такой стиль программирования может заметно упростить проектирование параллельной системы, поскольку все потоки рассматриваются как абсолютно независимые. Таким образом, мы имеем пример использования нескольких потоков для разделения обязанностей, а, значит, от нас требуется явно решить, как распределять между ними задачи.
4.5. Резюме
Синхронизация операций между потоками — важная часть написания параллельного приложения. Если синхронизации нет, то потоки ведут себя независимо, и их вполне можно было бы реализовать как отдельные приложения, запускаемые группой, потому что выполняют взаимосвязанные действия. В этой главе мы рассмотрели различные способы синхронизации операций — простые условные переменные, будущие результаты, обещания и упакованные задачи. Мы также обсудили несколько подходов к решению проблем синхронизации: функциональное программирование, когда каждая задача порождает результат, зависящий только от входных данных, но не от внешнего состояния, и передачу сообщений, когда взаимодействие потоков осуществляется за счет асинхронного обмена сообщениями с помощью какой-либо подсистемы передачи сообщений, играющей роль посредника.
Теперь, когда мы обсудили многие высокоуровневые средства, имеющиеся в С++, настало время познакомиться с низкоуровневыми механизмами, которые приводят всю систему в движение: модель памяти С++ и атомарные операции.
Глава 5.
Модель памяти С++ и атомарные операции