& msg) {
if (q){ │
Отправка сообщения сводится q->push(msg);←┘
к помещению его в очередь }
}
};
}
Получение сообщений несколько сложнее. Мы не только должны дождаться появления сообщения в очереди, но еще и проверить, совпадает ли его тип с одним из известных нам типов, и вызвать соответствующий обработчик. Эта процедура начинается в классе
receiver
, показанном в листинге ниже.
Листинг С.3. Класс
receiver
namespace messaging {
class receiver {
queue q; ←
receiver владеет очередью
public: │
Разрешить неявное преобразование в объект operator sender() {←┘
sender, ссылающийся на эту очередь return sender(&q);
}
│
При обращении к функции ожидания dispatcher wait() {←┘
очереди создается диспетчер return dispatcher(&q);
}
};
}
Если
sender
только ссылается на очередь сообщений, то receiver
ей владеет. Мы можем получить объект sender
, ссылающийся на очередь, воспользовавшись неявным преобразованием. Процедура диспетчеризации сообщения начинается с обращения к функции wait()
. При этом создается объект dispatcher
, ссылающийся на очередь, которой владеет receiver
. Класс dispatcher
показан в следующем листинге; как видите, содержательная работа производится в его деструкторе. В данном случае работа состоит в ожидании сообщения и его диспетчеризации.
Листинг С.4. Класс
dispatcher
namespace messaging {
class close_queue {}; ←
Сообщение о закрытии очереди
class dispatcher {
queue* q; │
Экземпляры bool chained; │
диспетчера нельзя│
копировать dispatcher(dispatcher const&)=delete;←┘
dispatcher& operator=(dispatcher const&)=delete;
template<
typename Dispatcher,│
Разрешить экземплярам typename Msg, │
TemplateDispatcher доступ typename Func>←┘
к закрытым частям класса friend class TemplateDispatcher;
void wait_and_dispatch()
{
(1) В цикле ждем и диспетчеризуем for (;;) {←┘
сообщения auto msg = q->wait_and_pop();
dispatch(msg);
}
}
(2) dispatch() смотрит, не пришло ли│
сообщение close_queue, и, если bool dispatch (←┘
да, возбуждает исключение std::shared_ptr const& msg) {
if (dynamic_cast*>(msg.get())) {
throw close_queue();
}
return false;
}
public: │
Экземпляры диспетчера dispatcher(dispatcher&& other):←┘
можно перемещать q(other.q), chained(other.chained) {│
Объект-источник не должен other.chained = true; ←┘
ждать сообщений }
explicit dispatcher(queue* q_): q(q_), chained(false) {}
template
TemplateDispatcher
handle(Func&& f)←┐
Сообщения конкретного типа {
(3) обрабатывает TemplateDispatcher return TemplateDispatcher(
q, this, std::forward(f));
}
~dispatcher() noexcept(false)←┐
Деструктор может {
(4) возбудить исключение if (!chained) {
wait_and_dispatch();
}
}
};
}
Экземпляр
dispatcher
, возвращенный функцией wait()
, немедленно уничтожается, так как является временным объектом, и, как уже было сказало, вся работа выполняется в его деструкторе. Деструктор вызывает функцию wait_and_dispatch()
, которая в цикле (1) ожидает сообщения и передает его функции dispatch()
. Сама функция dispatch()
(2) проста, как правда: она проверяет, не получено ли сообщение типа close_queue
, и, если так, то возбуждает исключение; в противном случае возвращает false
, извещая, что сообщение не обработало. Именно из-за исключения close_queue
деструктор и помечен как noexcept(false)
(4); без этой аннотации действовала бы подразумеваемая спецификация исключений для деструктора — noexcept(true)
, означающая, что исключения не допускаются, и тогда исключение close_queue
привело бы к завершению программы.Но просто вызывать функцию
wait()
особого смысла не имеет — как правило, нам нужно обработать полученное сообщение. Для этого предназначена функция-член handle()
(3). Это шаблон, и тип сообщения нельзя вывести, поэтому необходимо явно указать, сообщение какого типа обрабатывается, и передать функцию (или допускающий вызов объект) для его обработки. Сама функция handle()
передает очередь, текущий объект dispatcher
и функцию-обработчик новому экземпляру шаблонного класса TemplateDispatcher
, который обрабатывает сообщения указанного типа. Код этого класса показан в листинге С.5. Именно поэтому мы проверяем флаг chained
в деструкторе перед тем, как приступить к ожиданию сообщения; он не только предотвращает ожидание объектами, содержимое которых перемещено, но и позволяет передать ответственность за ожидание новому экземпляру TemplateDispatcher
.
Листинг С.5. Шаблон класса
TemplateDispatcher
namespace messaging {
template<
typename PreviousDispatcher, typename Msg, typename Func>
class TemplateDispatcher {
queue* q;
PreviousDispatcher* prev;
Func f;
bool chained;
TemplateDispatcher(TemplateDispatcher const&) = delete;
TemplateDispatcher& operator=(
TemplateDispatcher const&) = delete;
template<
typename Dispatcher, typename OtherMsg, typename OtherFunc>
friend class TemplateDispatcher;←┐
Все конкретизации void wait_and_dispatch() │
TemplateDispatcher { │
дружат между собой for (;;) {
auto msg = q-
Оглавление
-
Предисловие
-
Благодарности
-
Об этой книге
-
Об иллюстрации на обложке
-
Глава 1. Здравствуй, параллельный мир!
-
1.1. Что такое параллелизм?
-
1.1.1. Параллелизм в вычислительных системах
-
1.1.2. Подходы к организации параллелизма
-
1.2. Зачем нужен параллелизм?
-
1.2.1. Применение параллелизма для разделения обязанностей
-
1.2.2. Применение параллелизма для повышения производительности
-
1.2.3. Когда параллелизм вреден?
-
1.3. Параллелизм и многопоточность в С++
-
1.3.1. История многопоточности в С++
-
1.3.2. Поддержка параллелизма в новом стандарте
-
1.3.3. Эффективность библиотеки многопоточности для С++
-
1.3.4. Платформенно-зависимые средства
-
1.4. В начале пути
-
1.4.1. Здравствуй, параллельный мир
-
1.5. Резюме
-
Глава 2. Управление потоками
-
2.1. Базовые операции управления потоками
-
2.1.1. Запуск потока
-
2.1.2. Ожидание завершения потока
-
2.1.3. Ожидание в случае исключения
-
2.1.4. Запуск потоков в фоновом режиме
-
2.2. Передача аргументов функции потока
-
2.3. Передача владения потоком
-
2.4. Задание количества потоков во время выполнения
-
2.5. Идентификация потоков
-
2.6. Резюме
-
Глава 3. Разделение данных между потоками
-
3.1. Проблемы разделения данных между потоками
-
3.1.1. Гонки
-
3.1.2. Устранение проблематичных состояний гонки
-
3.2. Защита разделяемых данных с помощью мьютексов
-
3.2.1. Использование мьютексов в С++
-
3.2.2. Структурирование кода для защиты разделяемых данных
-
3.2.3. Выявление состояний гонки, внутренне присущих интерфейсам
-
3.2.4. Взаимоблокировка: проблема и решение
-
3.2.5. Дополнительные рекомендации, как избежать взаимоблокировок
-
3.2.6. Гибкая блокировка с помощью std::unique_lock
-
3.2.7. Передача владения мьютексом между контекстами
-
3.2.8. Выбор правильной гранулярности блокировки
-
3.3. Другие средства защиты разделяемых данных
-
3.3.1. Защита разделяемых данных во время инициализации
-
3.3.2. Защита редко обновляемых структур данных
-
3.3.3. Рекурсивная блокировка
-
3.4. Резюме
-
Глава 4. Синхронизация параллельных операций
-
4.1. Ожидание события или иного условия
-
4.1.1. Ожидание условия с помощью условных переменных
-
4.1.2. Потокобезопасная очередь на базе условных переменных
-
4.2. Ожидание одноразовых событий с помощью механизма будущих результатов
-
4.2.1. Возврат значения из фоновой задачи
-
4.2.2. Ассоциирование задачи с будущим результатом
-
4.2.3. Использование std::promise
-
4.2.4. Сохранение исключения в будущем результате
-
4.2.5. Ожидание в нескольких потоках
-
4.3. Ожидание с ограничением по времени
-
4.3.1. Часы
-
4.3.2. Временные интервалы
-
4.3.3. Моменты времени
-
4.3.4. Функции, принимающие таймаут
-
4.4. Применение синхронизации операций для упрощения кода
-
4.4.1. Функциональное программирование с применением будущих результатов
-
4.4.2. Синхронизация операций с помощью передачи сообщений
-
4.5. Резюме
-
Глава 5. Модель памяти С++ и атомарные операции
-
5.1. Основы модели памяти
-
5.1.1. Объекты и ячейки памяти
-
5.1.2. Объекты, ячейки памяти и параллелизм
-
5.1.3. Порядок модификации
-
5.2. Атомарные операции и типы в С++
-
5.2.1. Стандартные атомарные типы
-
5.2.2. Операции над std::atomic_flag
-
5.2.3. Операции над std::atomic < bool >
-
5.2.4. Операции над std::atomic < T* > : арифметика указателей
-
5.2.5. Операции над стандартными атомарными целочисленными типами
-
5.2.6. Основной шаблон класса std::atomic < >
-
5.2.7. Свободные функции для атомарных операций
-
5.3. Синхронизация операций и принудительное упорядочение
-
5.3.1. Отношение синхронизируется-с
-
5.3.2. Отношение происходит-раньше
-
5.3.3. Упорядочение доступа к памяти для атомарных операций
-
5.3.4. Последовательности освобождений и отношение синхронизируется-с
-
5.3.5. Барьеры
-
5.3.6. Упорядочение неатомарных операций с помощью атомарных
-
5.4. Резюме
-
Глава 6 Проектирование параллельных структур данных с блокировками
-
6.1. Что понимается под проектированием структур данных, рассчитанных на параллельный доступ?
-
6.1.1. Рекомендации по проектированию структур данных для параллельного доступа
-
6.2. Параллельные структуры данных с блокировками
-
6.2.1. Потокобезопасный стек с блокировками
-
6.2.2. Потокобезопасная очередь с блокировками и условными переменными
-
6.2.3. Потокобезопасная очередь с мелкогранулярными блокировками и условными переменными
-
6.3. Проектирование более сложных структур данных с блокировками
-
6.3.1. Разработка потокобезопасной справочной таблицы с блокировками
-
6.3.2. Потокобезопасный список с блокировками
-
6.4. Резюме
-
Глава 7 Проектирование параллельных структур данных без блокировок
-
7.1. Определения и следствия из них
-
7.1.1. Типы неблокирующих структур данных
-
7.1.2. Структуры данных, свободные от блокировок
-
7.1.3. Структуры данных, сво бодные от ожидания
-
7.1.4. Плюсы и минусы структур данных, свободных от блокировок
-
7.2. Примеры структур данных, свободных от блокировок
-
7.2.1. Потокобезопасный стек без блокировок
-
7.2.2. Устранение утечек: управление памятью в структурах данных без блокировок
-
7.2.3. Обнаружение узлов, не подлежащих освобождению, с помощью указателей опасности
-
7.2.4. Нахождение используемых узлов с помощью подсчета ссылок
-
7.2.5. Применение модели памяти к свободному от блокировок стеку
-
7.2.6. Потокобезопасная очередь без блокировок
-
7.3. Рекомендации по написанию структур данных без блокировок
-
7.3.1. Используйте std::memory_order_seq_cst для создания прототипа
-
7.3.2. Используйте подходящую схему освобождения памяти
-
7.3.3. Помните о проблеме ABA
-
7.3.4. Выявляйте циклы активного ожидания и помогайте другим потокам
-
Глава 8. Проектирование параллельных программ
-
8.1. Методы распределения работы между потоками
-
8.1.1. Распределение данных между потоками до начала обработки
-
8.1.2. Рекурсивное распределение данных
-
8.1.3. Распределение работы по типам задач
-
8.2. Факторы, влияющие на производительность параллельного кода
-
8.2.1. Сколько процессоров?
-
8.2.2. Конкуренция за данные и перебрасывание кэша
-
8.2.3. Ложное разделение
-
8.2.4. Насколько близки ваши данные?
-
8.2.5. Превышение лимита и чрезмерное контекстное переключение
-
8.3. Проектирование структур данных для повышения производительности многопоточной программы
-
8.3.1. Распределение элементов массива для сложных операций
-
8.3.2. Порядок доступа к другим структурам данных
-
8.4. Дополнительные соображения при проектировании параллельных программ
-
8.4.1. Безопасность относительно исключений в параллельных алгоритмах
-
8.4.2. Масштабируемость и закон Амдала
-
8.4.3. Сокрытие латентности с помощью нескольких потоков
-
8.4.4. Повышение быстроты реакции за счет распараллеливания
-
8.5. Проектирование параллельного кода на практике
-
8.5.1. Параллельная реализация std::for_each
-
8.5.2. Параллельная реализация std::find
-
8.5.3. Параллельная реализация std::partial_sum
-
8.6. Резюме
-
Глава 9. Продвинутое управление потоками
-
9.1. Пулы потоков
-
9.1.1. Простейший пул потоков
-
9.1.2. Ожидание задачи, переданной пулу потоков
-
9.1.3. Задачи, ожидающие других задач
-
9.1.4. Предотвращение конкуренции за очередь работ
-
9.1.5. Занимание работ
-
9.2. Прерывание потоков
-
9.2.1. Запуск и прерывание другого потока
-
9.2.2. Обнаружение факта прерывания потока
-
9.2.3. Прерывание ожидания условной переменной
-
9.2.4. Прерывание ожидания std::condition_variable_any
-
9.2.5. Прерывание других блокирующих вызовов
-
9.2.6. О бработка прерываний
-
9.2.7. Прерывание фоновых потоков при выходе из приложения
-
9.3. Резюме
-
Глава 10. Тестирование и отладка многопоточных приложений
-
10.1. Типы ошибок, связанных с параллелизмом
-
10.1.1. Нежелательное блокирование
-
10.1.2. Состояния гонки
-
10.2. Методы поиска ошибок, связанных с параллелизмом
-
10.2.1. Анализ кода на предмет выявления потенциальных ошибок
-
10.2.2. Поиск связанных с параллелизмом ошибок путем тестирования
-
10.2.3. Проектирование с учетом тестопригодности
-
10.2.4. Приемы тестирования многопоточного кода
-
10.2.5. Структурирование многопоточного тестового кода
-
10.2.6. Тестирование производительности многопоточного кода
-
10.3. Резюме
-
Приложение А. Краткий справочник по некоторым конструкциям языка С++
-
А.1. Ссылки на r -значения
-
A.1.1. Семантика перемещения
-
А.1.2. Ссылки на r -значения и шаблоны функций
-
А.2. Удаленные функции
-
А.3. Умалчиваемые функции
-
А.4. constexpr -функции
-
А.4.1. constexpr и определенные пользователем типы
-
А.4.2. constexpr -объекты
-
A.4.3. Требования к constexpr -функциям
-
А.4.4. constexpr и шаблоны
-
А.5. Лямбда-функции
-
A.5.1. Лямбда-функции, ссылающиеся на локальные переменные
-
А.6. Шаблоны с переменным числом параметров
-
A.6.1. Расширение пакета параметров
-
А.7. Автоматическое выведение типа переменной
-
А.8. Поточно-локальные переменные
-
А.9. Резюме
-
Приложение В. Краткое сравнение библиотек для написания параллельных программ
-
Приложение С. Каркас передачи сообщений и полный пример программы банкомата
-
***
-
***
-
***
-
***
-
Приложение D Справочник по библиотеке С++ Thread Library
-
D.1. Заголовок < chrono >
-
D.1.1. Шаблон класса std::chrono::duration
-
D.1.2. Шаблон класса std::chrono::time_point
-
D.1.3. Класс std::chrono::system_clock
-
D.1.4. Класс std::chrono::steady_clock
-
D.1.5. Псевдоним типа std::chrono::high_resolution_clock
-
D.2. Заголовок < condition_variable >
-
D.2.1. Класс std::condition_variable
-
D.2.2. Класс std::condition_variable_any
-
D.3. Заголовок < atomic >
-
D.3.1. std::atomic_xxx , псевдонимы типов
-
D.3.2. ATOMIC_ xxx _LOCK_FREE , макросы
-
D.3.3. ATOMIC_VAR_INIT , макрос
-
D.3.4. std::memory_order , перечисление
-
D.3.5. std::atomic_thread_fence , функция
-
D.3.6. std::atomic_signal_fence , функция
-
D.3.7. std::atomic_flag , класс
-
D.3.8. Шаблон класса std::atomic
-
D.3.9. Специализации шаблона std::atomic
-
D.3.10. Специализации std::atomic < integral-type >
-
D.4. Заголовок < future >
-
D.4.1. Шаблон класса std::future
-
D.4.2. Шаблон класса std::shared_future
-
D.4.3. Шаблон класса std::packaged_task
-
D.4.4. Шаблон класса std::promise
-
D.4.5. Шаблон функции std::async
-
D.5. Заголовок < mutex >
-
D.5.1. Класс std::mutex
-
D.5.2. Класс std::recursive_mutex
-
D.5.3. Класс std::timed_mutex
-
D.5.4. Класс std::recursive_timed_mutex
-
D.5.5. Шаблон класса std::lock_guard
-
D.5.6. Шаблон класса std::unique_lock
-
D.5.7. Шаблон функции std::lock
-
D.5.8. Шаблон функции std::try_lock
-
D.5.9. Класс std::once_flag
-
D.5.10. Шаблон функции std::call_once
-
D.6. Заголовок < ratio >
-
D.6.1. Шаблон класса std::ratio
-
D.6.2. Псевдоним шаблона std::ratio_add
-
D.6.3. Псевдоним шаблона std::ratio_subtract
-
D.6.4. Псевдоним шаблона std::ratio_multiply
-
D.6.5. Псевдоним шаблона std::ratio_divide
-
D.6.6. Шаблон класса std::ratio_equal
-
D.6.7. Шаблон класса std::ratio_not_equal
-
D.6.8. Шаблон класса std::ratio_less
-
D.6.9. Шаблон класса std::ratio_greater
-
D.6.10. Шаблон класса std::ratio_less_equal
-
D.6.11. Шаблон класса std::ratio_greater_equal
-
D.7. Заголовок < thread >
-
D.7.1. Класс std::thread
-
D.7.2. Пространство имен this_thread
-
Ресурсы
-
Печатные ресурсы
-
Сетевые ресурсы
-
Примечания