Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Название:Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-448-1
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ краткое содержание
Книга «Параллельное программирование на С++ в действии» не предполагает предварительных знаний в этой области. Вдумчиво читая ее, вы научитесь писать надежные и элегантные многопоточные программы на С++11. Вы узнаете о том, что такое потоковая модель памяти, и о том, какие средства поддержки многопоточности, в том числе запуска и синхронизации потоков, имеются в стандартной библиотеке. Попутно вы познакомитесь с различными нетривиальными проблемами программирования в условиях параллелизма.
Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Листинг 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.
Модель памяти С++ и атомарные операции
■ Детальные сведения о модели памяти С++.
■ Атомарные типы в стандартной библиотеке С++.
■ Операции над атомарными типами.
■ Как можно использовать эти операции для синхронизации потоков.
Одна из самых важных особенностей стандарта С++11 — та, которую большинство программистов даже не замечают. Это не новые синтаксические конструкции и не новые библиотечные средства, а новая модель памяти, учитывающая многопоточность. Без модели памяти, которая точно определяет, как должны работать основополагающие строительные блоки, ни на одно из описанных выше средств нельзя было бы полагаться. Понятно, почему большинство программистов этого не замечают: если вы пользуетесь для защиты данных мьютексами, а для сигнализации о событиях — условными переменными или будущими результатам, то вопрос о том, почему они работают, не так уж важен. И лишь когда вы подбираетесь «ближе к железу», становятся существенны точные детали модели памяти.
С++ используется для решения разных задач, но одна из основных — системное программирование. Поэтому комитет по стандартизации в числе прочих целей ставил и такую: сделать так, чтобы в языке более низкого уровня, чем С++, не возникало необходимости. С++ должен обладать достаточной гибкостью, чтобы программист мог сделать то, что хочет, без помех со стороны языка, в том числе и работать «на уровне железа». Атомарные типы и операции — шаг именно в этом направлении, поскольку они предоставляют низкоуровневые механизмы синхронизации, которые обычно транслируются в одну-две машинные команды.
В этой главе мы начнем с рассмотрения основ модели памяти, затем перейдем к атомарным типам и операциям и в конце обсудим различные виды синхронизации, реализуемые с помощью операций над атомарными типами. Это довольно сложная тема; если вы не планируете писать код, в котором атомарные операции используются для синхронизации (например, структуры данных без блокировок, рассматриваемые в главе 7), то все эти детали вам ни к чему. Но давайте потихоньку двинемся вперёд и начнем с модели памяти.
5.1. Основы модели памяти
У модели памяти есть две стороны: базовые структурные аспекты, относящиеся к размещению программы в памяти, и аспекты, связанные с параллелизмом. Структурные аспекты важны для параллелизма, особенно если опуститься на низкий уровень атомарных операций, поэтому с них я и начну. В С++ всё вращается вокруг объектов и ячеек памяти.
5.1.1. Объекты и ячейки памяти
Любые данные в программе на С++ состоят из объектов. Это не значит, что можно создать новый класс, производный от int
, или что у фундаментальных типов есть функции-члены, или вообще нечто такое, что часто имеют в виду, когда говорят «нет ничего, кроме объектов» при обсуждении таких языков, как Smalltalk или Ruby. Это утверждение просто означает, что в С++ данные строятся из объектов. В стандарте С++ объект определяется как «область памяти», хотя далее речь идет о таких свойствах объектов, как тип и время жизни.
Некоторые объекты являются простыми значениями таких фундаментальных типов, как int
или float
, другие — экземплярами определенных пользователем классов. У некоторых объектов (например, массивов, экземпляров производных классов и экземпляров классов с нестатическими данными-членами) есть подобъекты, у других — нет.
Вне зависимости от типа объект хранится в одной или нескольких ячейках памяти . Каждая такая ячейка — это либо объект (или подобъект) скалярного типа, например unsigned short
или my_class*
, либо последовательность соседних битовых полей. Если вы пользуетесь битовыми полями, то имейте в виду один важный момент: хотя соседние битовые поля является различными объектами, они тем не менее считаются одной ячейкой памяти. На рис. 5.1 показано, как структура struct
представлена в виде совокупности объектов и ячеек памяти.
Интервал:
Закладка: