Сама лямбда-функция просто запоминает номер карточного счета в переменной-члене, очищает текущий ПИН-код и переходит в состояние «получение ПИН». По завершении обработчика сообщений функция состояния возвращает управление главному циклу, который вызывает функцию следующего состояния (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. Каждое состояние реализовано отдельной функцией-членом, которая ждет сообщений определенных типов и при необходимости обновляет состояние.
Как видите, такой стиль программирования может заметно упростить проектирование параллельной системы, поскольку все потоки рассматриваются как абсолютно независимые. Таким образом, мы имеем пример использования нескольких потоков для разделения обязанностей, а, значит, от нас требуется явно решить, как распределять между ними задачи.
Синхронизация операций между потоками — важная часть написания параллельного приложения. Если синхронизации нет, то потоки ведут себя независимо, и их вполне можно было бы реализовать как отдельные приложения, запускаемые группой, потому что выполняют взаимосвязанные действия. В этой главе мы рассмотрели различные способы синхронизации операций — простые условные переменные, будущие результаты, обещания и упакованные задачи. Мы также обсудили несколько подходов к решению проблем синхронизации: функциональное программирование, когда каждая задача порождает результат, зависящий только от входных данных, но не от внешнего состояния, и передачу сообщений, когда взаимодействие потоков осуществляется за счет асинхронного обмена сообщениями с помощью какой-либо подсистемы передачи сообщений, играющей роль посредника.
Теперь, когда мы обсудили многие высокоуровневые средства, имеющиеся в С++, настало время познакомиться с низкоуровневыми механизмами, которые приводят всю систему в движение: модель памяти С++ и атомарные операции.
Глава 5.
Модель памяти С++ и атомарные операции
В этой главе:
■ Детальные сведения о модели памяти С++.
■ Атомарные типы в стандартной библиотеке С++.
■ Операции над атомарными типами.
■ Как можно использовать эти операции для синхронизации потоков.
Одна из самых важных особенностей стандарта С++11 — та, которую большинство программистов даже не замечают. Это не новые синтаксические конструкции и не новые библиотечные средства, а новая модель памяти, учитывающая многопоточность. Без модели памяти, которая точно определяет, как должны работать основополагающие строительные блоки, ни на одно из описанных выше средств нельзя было бы полагаться. Понятно, почему большинство программистов этого не замечают: если вы пользуетесь для защиты данных мьютексами, а для сигнализации о событиях — условными переменными или будущими результатам, то вопрос о том, почему они работают, не так уж важен. И лишь когда вы подбираетесь «ближе к железу», становятся существенны точные детали модели памяти.
Читать дальше