ЛЕКЦИЯ 5. Шаблоны многопоточного программирования
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...Alexey Paznikov
ЛЕКЦИЯ 5. Многопоточное программирование в языке С++. Работа с потоками. Защита данных. Синхронизация. Будущие результаты
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ЛЕКЦИЯ 3. Реентерабельность. Сигналы. Локальные данные потоков. Принудительное завершение потоков
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ЛЕКЦИЯ 8. Многопоточное программирование без использования блокировок. Модель потребитель-производитель. Потокобезопасный стек. Проблема ABA. Указатели опасности.
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...Alexey Paznikov
ЛЕКЦИЯ 6. Разработка параллельных структур данных на основе блокировок
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...Alexey Paznikov
ЛЕКЦИЯ 6. Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель памяти C++
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ЛЕКЦИЯ 4. Шаблоны многопоточного программирования
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ЛЕКЦИЯ 4. Стандарт POSIX Threads. Реентерабельность функций. Обработка сигналов. Локальные данные потоков. Принудительное завершение потоков. Шаблоны программирования с использованием потоков
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ЛЕКЦИЯ 7. Многопоточное программирование без блокировок. Модель потребитель-производитель. Потокобезопасный стек: проблема ABA, указатели опасности, сборщики мусора, счётчик ссылок, применение модели памяти С++.
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...Alexey Paznikov
ЛЕКЦИЯ 5. Многопоточное программирование в языке С++. Работа с потоками. Защита данных. Синхронизация. Будущие результаты
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ЛЕКЦИЯ 3. Реентерабельность. Сигналы. Локальные данные потоков. Принудительное завершение потоков
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ЛЕКЦИЯ 8. Многопоточное программирование без использования блокировок. Модель потребитель-производитель. Потокобезопасный стек. Проблема ABA. Указатели опасности.
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...Alexey Paznikov
ЛЕКЦИЯ 6. Разработка параллельных структур данных на основе блокировок
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...Alexey Paznikov
ЛЕКЦИЯ 6. Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель памяти C++
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ЛЕКЦИЯ 4. Шаблоны многопоточного программирования
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ЛЕКЦИЯ 4. Стандарт POSIX Threads. Реентерабельность функций. Обработка сигналов. Локальные данные потоков. Принудительное завершение потоков. Шаблоны программирования с использованием потоков
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ЛЕКЦИЯ 7. Многопоточное программирование без блокировок. Модель потребитель-производитель. Потокобезопасный стек: проблема ABA, указатели опасности, сборщики мусора, счётчик ссылок, применение модели памяти С++.
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
Доклад Кулагина И.И., Пазникова А.А., Курносова М.Г. "Оптимизация информационных обменов в параллельных PGAS-программах" на 3-й Всероссийской научно-технической конференции «Суперкомпьютерные технологии» (СКТ-2014)
29 сентября – 4 октября 2014 г., с. Дивноморское
ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...Alexey Paznikov
ЛЕКЦИЯ 2. POSIX Threads. Жизненный цикл потоков. Планирование. Синхронизация
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Sergey Platonov
В последнее время в промышленной разработке ПО особую популярность обретают Domain-Specific Lanugages (DSL). Они драматически упрощают разработку и дают возможность “программировать” не только программистам, но и пользователям прикладных программ.
В своем докладе я расскажу об опыте использования DSL применительно к С++, причем упор будет сделан на производительность кода DSL, и его мгновенную “встраиваемость” в запущенную программу путем компиляции DSL-кода в нативный код с помощью инструментария LLVM.
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMSergey Platonov
Зачастую, знакомство с алиасингом в C++ у многих программистов начинается и заканчивается одинаково: -fno-strict-aliasing. На вопросы новичка, более опытные коллеги отвечают в стиле: «не трогай! а то все сломаешь!». Новичок и не трогает. В докладе будет предпринята попытка заглянуть под капот и понять, что же там, внутри. Что такое алиасинг, где он может быть полезен и какие реальные преимущества дает. Тема будет рассмотрена и со стороны программиста и со стороны разработчика компилятора. А по сему, вопрос «зачем?» будет центральным в повествовании.
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловSergey Platonov
В докладе перед нами откроется великолепный мир велосипедов и устаревших технологий, которые люди продолжают переносить в новые проекты и повсеместно использовать. Мы поговорим о:
Copy-On-Write
разработке без оглядки на готовые решения и к чему это приводит
force inline
оптимизациях, которые отлично себя показывают на бенчмарках и плохо себя ведут в реальной жизни
бездумно отключаемых оптимизациях компилятора
тонкостях стандартной библиотеки для повседневного использования
супер качественном велосипедостроении
Догнать и перегнать boost::lexical_castRoman Orlov
Разбор нестандартной реализации преобразования целого числа в строку без использования циклов и рекурсивных вызовов времени исполнения - только рекурсия на этапе компиляции
Григорий Демченко, Асинхронность и неблокирующая синхронизацияSergey Platonov
Практика показывает, что использование подхода, основанного на колбеках для асинхронного программирования обычно не является удобным и подвержено различным ошибкам. Для упрощения написания и поддержки сложных асинхронных программ можно использовать несколько иной подход: использовать сопрограммы для переключения контекста на время ожидания события. Такой подход позволяет реализовать интересные неблокирующие примитивы, включая неблокирующее сетевое взаимодействие, неблокирующие мьютексы, а также удобное переключение между различными пулами потоков для разнесения выполнения задач, которые требуют различные ресурсы.
ЛЕКЦИЯ 0. Описание курса. Общие вопросы, структура курса, требования. Содержание курса. Полезные ресурсы
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»Platonov Sergey
За время работы над проектом был разработан удобный фреймворк для написания асинхронного кода. В докладе будет рассмотрено то, как он устроен и как со временем эволюционировал. Разберемся, как с помощью наших примитивов решать проблемы, часто встречающиеся в асинхронном программирование; будут примеры удачного и неудачного использования. Отдельно остановимся на сравнении получившихся средств с тем, что было добавлено в С++11.
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...Alexey Paznikov
ЛЕКЦИЯ 7. Модель памяти С++. Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
Использование юнит-тестов для повышения качества разработкиvictor-yastrebov
В докладе рассмотрены подходы к созданию надежных юнит-тестов, которые просты в поддержке и модернизации, а также принципы создания кода пригодного для покрытия автотестами. Приведены два способа внедрения зависимости: с использованием конструктора тестируемого объекта, а также с использованием подхода "выделить и переопределить". Каждый из способов разобран на примере, демонстрирующем особенности его реализации и применения. Приведен ряд практических советов, нацеленных на создание надежных юнит-тестов. Использование на практике приведенных подходов и принципов позволяет упростить процесс поддержки и модификации существующего кода, а также дает уверенность в надежности работы добавляемого нового функционала. В конечном итоге это приводит к повышению качества разрабатываемого продукта.
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...Alexey Paznikov
ЛЕКЦИЯ 2. Коллективные операции в стандарте MPI
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
http://cpct.sibsutis.ru/~apaznikov/teaching
Алексей Куканов — Параллелизм в C++: управляйте приложением, а не потоками!Yandex
Алексей Куканов, Intel.
Последняя версия стандарта С++ добавляет в язык и библиотеку поддержки средства для использования потоков исполнения (threads) и синхронизации между ними. Однако это лишь необходимая низкоуровневая база для внедрения параллелизма. Эффективная разработка параллельных программ требует высокоуровневого API, реализующего типичные шаблоны использования параллелизма в виде, пригодном для применения в широком спектре алгоритмов и приложений. В докладе речь пойдёт о наиболее часто встречающихся параллельных шаблонах, реализованных в программных моделях Intel® Threading Building Blocks и Intel® Cilk Plus, и о примерах их использования.
Доклад вводит в рассмотрение универсальный адаптер, позволяющий обернуть любой класс с целью добавления новых свойств, отсутствующих в оригинальном классе. Получаемые классы могут иметь в точности такой же интерфейс, как и первоначальные, что позволяет прозрачно заменять их и оборачивать любое количество раз.
Это позволяет добавлять необходимые свойства объектам, не переписывая его с нуля. Предложенная обобщенная концепция будет последовательно введена и проиллюстрирована простыми, но интересными примерами.
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Yandex
Основываясь на опыте разработки Крипты, Дмитрий рассмотрит средства реализации статического и динамического полиморфизма в C++, а также некоторые их паттерны и антипаттерны.
Статический и динамический полиморфизм в C++, Дмитрий ЛевановYandex
На примере некоторых архитектурных решений Крипты Дмитрий расскажет о способах реализации полиморфного поведения в программах на C++, о преимуществах и недостатках этих способов, а также о новых возможностях C++11.
Доклад Кулагина И.И., Пазникова А.А., Курносова М.Г. "Оптимизация информационных обменов в параллельных PGAS-программах" на 3-й Всероссийской научно-технической конференции «Суперкомпьютерные технологии» (СКТ-2014)
29 сентября – 4 октября 2014 г., с. Дивноморское
ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...Alexey Paznikov
ЛЕКЦИЯ 2. POSIX Threads. Жизненный цикл потоков. Планирование. Синхронизация
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Sergey Platonov
В последнее время в промышленной разработке ПО особую популярность обретают Domain-Specific Lanugages (DSL). Они драматически упрощают разработку и дают возможность “программировать” не только программистам, но и пользователям прикладных программ.
В своем докладе я расскажу об опыте использования DSL применительно к С++, причем упор будет сделан на производительность кода DSL, и его мгновенную “встраиваемость” в запущенную программу путем компиляции DSL-кода в нативный код с помощью инструментария LLVM.
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMSergey Platonov
Зачастую, знакомство с алиасингом в C++ у многих программистов начинается и заканчивается одинаково: -fno-strict-aliasing. На вопросы новичка, более опытные коллеги отвечают в стиле: «не трогай! а то все сломаешь!». Новичок и не трогает. В докладе будет предпринята попытка заглянуть под капот и понять, что же там, внутри. Что такое алиасинг, где он может быть полезен и какие реальные преимущества дает. Тема будет рассмотрена и со стороны программиста и со стороны разработчика компилятора. А по сему, вопрос «зачем?» будет центральным в повествовании.
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловSergey Platonov
В докладе перед нами откроется великолепный мир велосипедов и устаревших технологий, которые люди продолжают переносить в новые проекты и повсеместно использовать. Мы поговорим о:
Copy-On-Write
разработке без оглядки на готовые решения и к чему это приводит
force inline
оптимизациях, которые отлично себя показывают на бенчмарках и плохо себя ведут в реальной жизни
бездумно отключаемых оптимизациях компилятора
тонкостях стандартной библиотеки для повседневного использования
супер качественном велосипедостроении
Догнать и перегнать boost::lexical_castRoman Orlov
Разбор нестандартной реализации преобразования целого числа в строку без использования циклов и рекурсивных вызовов времени исполнения - только рекурсия на этапе компиляции
Григорий Демченко, Асинхронность и неблокирующая синхронизацияSergey Platonov
Практика показывает, что использование подхода, основанного на колбеках для асинхронного программирования обычно не является удобным и подвержено различным ошибкам. Для упрощения написания и поддержки сложных асинхронных программ можно использовать несколько иной подход: использовать сопрограммы для переключения контекста на время ожидания события. Такой подход позволяет реализовать интересные неблокирующие примитивы, включая неблокирующее сетевое взаимодействие, неблокирующие мьютексы, а также удобное переключение между различными пулами потоков для разнесения выполнения задач, которые требуют различные ресурсы.
ЛЕКЦИЯ 0. Описание курса. Общие вопросы, структура курса, требования. Содержание курса. Полезные ресурсы
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»Platonov Sergey
За время работы над проектом был разработан удобный фреймворк для написания асинхронного кода. В докладе будет рассмотрено то, как он устроен и как со временем эволюционировал. Разберемся, как с помощью наших примитивов решать проблемы, часто встречающиеся в асинхронном программирование; будут примеры удачного и неудачного использования. Отдельно остановимся на сравнении получившихся средств с тем, что было добавлено в С++11.
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...Alexey Paznikov
ЛЕКЦИЯ 7. Модель памяти С++. Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
Использование юнит-тестов для повышения качества разработкиvictor-yastrebov
В докладе рассмотрены подходы к созданию надежных юнит-тестов, которые просты в поддержке и модернизации, а также принципы создания кода пригодного для покрытия автотестами. Приведены два способа внедрения зависимости: с использованием конструктора тестируемого объекта, а также с использованием подхода "выделить и переопределить". Каждый из способов разобран на примере, демонстрирующем особенности его реализации и применения. Приведен ряд практических советов, нацеленных на создание надежных юнит-тестов. Использование на практике приведенных подходов и принципов позволяет упростить процесс поддержки и модификации существующего кода, а также дает уверенность в надежности работы добавляемого нового функционала. В конечном итоге это приводит к повышению качества разрабатываемого продукта.
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...Alexey Paznikov
ЛЕКЦИЯ 2. Коллективные операции в стандарте MPI
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
http://cpct.sibsutis.ru/~apaznikov/teaching
Алексей Куканов — Параллелизм в C++: управляйте приложением, а не потоками!Yandex
Алексей Куканов, Intel.
Последняя версия стандарта С++ добавляет в язык и библиотеку поддержки средства для использования потоков исполнения (threads) и синхронизации между ними. Однако это лишь необходимая низкоуровневая база для внедрения параллелизма. Эффективная разработка параллельных программ требует высокоуровневого API, реализующего типичные шаблоны использования параллелизма в виде, пригодном для применения в широком спектре алгоритмов и приложений. В докладе речь пойдёт о наиболее часто встречающихся параллельных шаблонах, реализованных в программных моделях Intel® Threading Building Blocks и Intel® Cilk Plus, и о примерах их использования.
Доклад вводит в рассмотрение универсальный адаптер, позволяющий обернуть любой класс с целью добавления новых свойств, отсутствующих в оригинальном классе. Получаемые классы могут иметь в точности такой же интерфейс, как и первоначальные, что позволяет прозрачно заменять их и оборачивать любое количество раз.
Это позволяет добавлять необходимые свойства объектам, не переписывая его с нуля. Предложенная обобщенная концепция будет последовательно введена и проиллюстрирована простыми, но интересными примерами.
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Yandex
Основываясь на опыте разработки Крипты, Дмитрий рассмотрит средства реализации статического и динамического полиморфизма в C++, а также некоторые их паттерны и антипаттерны.
Статический и динамический полиморфизм в C++, Дмитрий ЛевановYandex
На примере некоторых архитектурных решений Крипты Дмитрий расскажет о способах реализации полиморфного поведения в программах на C++, о преимуществах и недостатках этих способов, а также о новых возможностях C++11.
Rust: абстракции и безопасность, совершенно бесплатноOpen-IT
Владимир Матвеев: "Rust: абстракции и безопасность, совершенно бесплатно" (Обзор языка Rust: для чего он предназначен, его ключевые особенности, инфраструктура)
C++ CoreHard Autumn 2018. Полезный constexpr - Антон Полухинcorehard_by
В C++11 добавили новое ключевое слово - constexpr. Выглядит оно весьма невзрачно, да и на первый взгляд кажется, что смысла в нём маловато... Для чего же оно нужно, какие у него есть тайные супер способности и какую роль оно сыграет в дальнейшем развитии языка C++ - обо всём об этом мы и поговорим.
Статический анализ: вокруг Java за 60 минутAndrey Karpov
Статический анализ всё больше воспринимается как неотъемлемая часть процесса разработки качественного программного обеспечения. Разумеется, у этой технологии уже есть свои сторонники и противники, но, несмотря на это, тема статического анализа всё более актуальна и требует детального рассмотрения. Рассмотрим, что такое статический анализ, как он применяется и как влияет на качество и надёжность кода. Поговорим о важности раннего обнаружения ошибок и дефектов уязвимости. Рассмотрим существующие инструменты для Java, такие как Sonar Java, FindBugs и анализатор встроенном в среду разработки IntelliJ IDEA. Расскажем историю, почему несмотря на уже существующие инструменты, мы решили разработать PVS-Studio для Java, как мы это делали и что в итоге получилось. В конце затронем вопрос интеграции статических анализаторов кода в большие старые проекты. Другими словами, как увидеть 100500 срабатываний и не упасть духом.
Иван Пузыревский — Введение в асинхронное программированиеYandex
Доклад посвящен основам асинхронного программирования. Мы кратко обсудим историю вопроса: что такое асинхронность, где, почему и зачем она используется. Затем рассмотрим наиболее частые способы построения асинхронных интерфейсов: основанные на callback'ах и на future/promise. В ходе доклада выделим основные используемые концепции, посмотрим на их реализацию и примеры использования. А в конце поговорим о сложностях, которые часто встречаются в асинхронном программировании.
Павел Беликов, Как избежать ошибок, используя современный C++Sergey Platonov
Одной из проблем C++ является большое количество конструкций, поведение которых не определено или просто неожиданно для программиста. С такими ошибками мы часто сталкиваемся при разработке статического анализатора кода. Но, как известно, лучше всего находить ошибки ещё на этапе компиляции. На этом докладе мы поговорим о том, какие техники из современного C++ позволяют писать не только более простой и выразительный, но и безопасный код. Вы увидите ошибки в коде различных Open Source проектов и узнаете, как можно их избежать, используя новые стандарты
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...Yandex
Евгений Крутько, НИЦ «Курчатовский институт».
В докладе на примере программы моделирования динамики движения конструкций по методу конечных элементов рассматриваются возможности и практика распараллеливания вычислений. Речь в нём пойдёт как о технике создания новых вычислительных потоков, так и об использовании стандартов openMP и MPI.
Опыт разработки статического анализатора кодаAndrey Karpov
Один из основателей проекта PVS-Studio расскажет об опыте разработки статического анализатора кода C++. У инструментов статического анализа кода существует "проблема айсберга". От пользователей скрыты сложные механизмы анализа кода, и иногда им кажется, что статические анализаторы – это просто какие-то утилиты, ищущие опечатки с помощью регулярных выражений. Автор доклада постарается в общих чертах описать, как всё обстоит на самом деле. Он покажет на примерах, почему нормальный анализ с помощью регулярных выражений нереализуем, что такое Data Flow анализ, а также расскажет о других технологиях, применяемых при анализе кода. Вкратце будет затронут вопрос использования нейронных сетей, обсуждение которых сейчас является очень модной темой, и рассказано, почему с точки зрения анализа кода отношение к этому направлению является очень скептическим.
Дмитрий Прокопцев, Яндекс
Речь пойдёт о, наверное, одном из самых важных и в то же время сложных нововведений в С++11 — R-ссылках (rvalue references). Мы рассмотрим базовые правила работы с такими ссылками и связанные с ними новые концепции языка: перемещение классов, универсальные ссылки и перенаправление вызовов.
Андрей Карпов
Вы узнаете, что такое статический анализ кода и историю его развития. Узнаете, как эффективно применять инструменты статического анализа в своей работе, увидите практические примеры использования этой методологии. Доклад ориентирован на программистов, использующих языки Си/Си++, но будет полезен всем
Similar to ПВТ - весна 2015 - Лекция 5. Многопоточное программирование в С++. Синхронизация, будущие результаты (20)
ЛЕКЦИЯ 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллельный ввод-вывод в MPI
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
http://cpct.sibsutis.ru/~apaznikov/teaching
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)Alexey Paznikov
ЛЕКЦИЯ 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
http://cpct.sibsutis.ru/~apaznikov/teaching
Лекция 4. Производные типы данных в стандарте MPIAlexey Paznikov
ЛЕКЦИЯ 4. Производные типы данных в стандарте MPI
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
http://cpct.sibsutis.ru/~apaznikov/teaching
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...Alexey Paznikov
ЛЕКЦИЯ 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI умножения матрицы на вектор, метода Монте-Карло, решение линейных алгебраических уравнений (СЛАУ) методами Гаусса и сопряжённых градиентов
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
http://cpct.sibsutis.ru/~apaznikov/teaching
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обменыAlexey Paznikov
ЛЕКЦИЯ 1. Основные понятия стандарта MPI. Дифференцированные обмены
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
http://cpct.sibsutis.ru/~apaznikov/teaching
ЛЕКЦИЯ 1. Актуальность параллельных вычислений. Анализ параллельных алгоритмов. Многоядерные вычислительные систем с общей памятью
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ЛЕКЦИЯ 3. Стандарт POSIX Threads
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятьюAlexey Paznikov
ЛЕКЦИЯ 2. Архитектура вычислительных систем с общей памятью
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ЛЕКЦИЯ 1. Введение в параллельные вычисления
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ЛЕКЦИЯ 1а. Описание курса "Параллельные вычислительные технологии" (ПВТ), осень 2014
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных системСибирский государственный университеттелекоммуникаций и информатики
ЛЕКЦИЯ 11. Технико-экономическая эффективность функционирования вычислительных систем
Пазников Алексей Александрович
к.т.н., ст. преп. Кафедры вычислительных системСибирский государственный университеттелекоммуникаций и информатики
ЛЕКЦИЯ 10. Осуществимость решения задач на вычислительных системах
Пазников Алексей Александрович
к.т.н., ст. преп. Кафедры вычислительных системСибирский государственный университеттелекоммуникаций и информатики
ЛЕКЦИЯ 9. Показатели структурной живучести распределённых ВС
Пазников Алексей Александрович
к.т.н., ст. преп. Кафедры вычислительных системСибирский государственный университеттелекоммуникаций и информатики
ЛЕКЦИЯ 8. Расчёт функций потенциальной живучести распределённых вычислительных систем
Пазников Алексей Александрович
к.т.н., ст. преп. Кафедры вычислительных системСибирский государственный университеттелекоммуникаций и информатики
ЛЕКЦИЯ 7. Методика расчета показателей живучести ВС. Континуальный подход к анализу живучести большемасштабных ВС.
Пазников Алексей Александрович
к.т.н., ст. преп. Кафедры вычислительных системСибирский государственный университеттелекоммуникаций и информатики
ЛЕКЦИЯ 5. Живучие ВС. Потенциальная и структурная живучесть ВС. Показатели потенциальной живучести ВС
Пазников Алексей Александрович
к.т.н., ст. преп. Кафедры вычислительных системСибирский государственный университеттелекоммуникаций и информатики
ПВТ - весна 2015 - Лекция 5. Многопоточное программирование в С++. Синхронизация, будущие результаты
1. Лекция 5. Многопоточное
программирование в языке С++.
Работа с потоками. Защита данных.
Синхронизация. Будущие результаты
Пазников Алексей Александрович
Кафедра вычислительных систем СибГУТИ
Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/
Q/A: https://piazza.com/sibsutis.ru/spring2015/pct2015spring
Параллельные вычислительные технологии
Весна 2015 (Parallel Computing Technologies, PCT 15)
9. Запуск нескольких потоков и ожидание завершения
int main() {
std::vector<std::thread> threads;
for (auto i = 0; i < 10; i++) {
threads.push_back(std::thread([i](){
std::cout << i << "n"; }));
}
for_each(threads.begin(), threads.end(),
std::mem_fn(&std::thread::join));
}
9
10. Запуск нескольких потоков и идентификаторы потоков
std::vector<std::thread> threads;
std::map<std::thread::id, int> table;
for (auto i = 0; i < 10; i++) {
threads.push_back(std::thread([i](){
std::this_thread::sleep_for(
std::chrono::milliseconds(100 * i));
std::cout << i << "n";
}));
table.insert(std::make_pair(threads.back().get_id(),
i % 2));
}
std::cout << "value of 5: " << table[threads[5].get_id()]
<< std::endl;
std::cout << "value of 6: " << table[threads[6].get_id()]
<< std::endl;
for_each(threads.begin(), threads.end(),
std::mem_fn(&std::thread::join)); 10
31. Мьютексы в С++
class wrapper {
private:
data_t data; // защищаемые данные
std::mutex mut;
public:
template<typename Function>
void proc_data(Function func) {
std::lock_guard<std::mutex> lock(mut);
func(data); }
};
data_t *unprotected; // внешний указатель
void unsafe_func(data_t &protected) {
unprotected = &protected;
}
wrapper obj;
obj.proc_data(unsafe_func);
unprotected->do_something(); // незащищённый доступ к data
Любой код,
имеющий
доступ к
указателю или
ссылке, может
делать с ним
всё, что угодно,
не захватывая
мьютекс.
31
32. Мьютексы в С++
class wrapper {
private:
data_t data; // защищаемые данные
std::mutex mut;
public:
template<typename Function>
void proc_data(Function func) {
std::lock_guard<std::mutex> lock(mut);
func(data); }
};
data_t *unprotected; // внешний указатель
void unsafe_func(data_t &protected) {
unprotected = &protected;
}
wrapper obj;
obj.proc_data(unsafe_func);
unprotected->do_something(); // незащищённый доступ к data
Любой код,
имеющий
доступ к
указателю или
ссылке, может
делать с ним
всё, что угодно,
не захватывая
мьютекс.
Нельзя передавать указатели и
ссылки на защищённые данные
за пределы области видимости
блокировки никаким образом.
32
35. Адаптация интерфейсов к многопоточности
template <...> class stack {
public:
// ...
bool empty() const;
size_t size() const;
T& top();
T const &top() const;
void push(T const&);
void push(T&&);
void pop();
void swap(stack&&);
};
некорректный
результат
как решить?
stack<int> s;
if (!s.empty()) {
int const value = s.top();
s.pop();
// ...
}
35
36. Адаптация интерфейсов к многопоточности
std::vector<int> result;
mystack.pop(result);
1. Передавать ссылку в функцию pop
2. Потребовать наличия копирующего или перемещающего
конструктора, не возбуждающего исключений (доказано, что
можно объединить pop и top, но это можно сделать только если
конструкторы не вызывают исключений)
3. Возвращать указатель на вытолкнутый элемент
4. Одновременно 1 и один из вариантов 2 или 3
std::shared_ptr<T> pop()
36
45. Блокировка с помощью std::unique_lock
class Widget {
int val;
std::mutex m;
int getval() const {
return val;
}
};
bool Cmp(Widget &lhs, Widget &rhs) {
// не захватываем пока мьютексы
std::unique_lock<std::mutex> lock1(lhs.m,std::defer_lock);
std::unique_lock<std::mutex> lock2(rhs.m,std::defer_lock);
// а вот сейчас захватываем, причём без дедлоков
std::lock(lock1, lock2);
return lhs.getval() > rhs.getval() ? true : false;
}
45
46. Блокировка с помощью std::unique_lock
class Widget {
int val;
std::mutex m;
int getval() const {
std::lock_guard<std::mutex> lock(m);
return val;
}
};
bool Cmp(Widget &lhs, Widget &rhs) {
// обе операции совершаются под защитой мьютекса
int const lhs_val = lhs.getval();
int const rhs_val = rhs.getval();
std::lock(lock1, lock2);
return lhs_val > rhs_val ? true : false;
}
Минимизация
гранулярности
блокировки!
46
47. Блокировка с помощью std::unique_lock
void pop_and_process() {
std::unique_lock<std::mutex> lock(mut);
Widget data = queue.pop(); // получить элемент данных
lock.unlock(); // освободить мьютекс
super_widget result = process(data); // обработать данные
lock.lock(); // опять захватить мьютекс
output_result(data, result); // вывести результат
}
Минимизация блокировок!
▪ блокировать данные, а не операции
▪ удерживать мьютекс столько, сколько необходимо
▫ тяжёлые операции (захват другого мьютекса,
ввод/вывод и т.д.) - вне текущей критической секции
47
48. Однократный вызов и отложенная инициализация
class NetFacility {
private:
connect_handle connection;
bool connection_flag;
void open_connection() {
connection = connect_manager.open();
}
public:
NetFacility(connect_info &_info): {}
void send_data(data_packet const &d) {
// отложенная инициализация
if (connection_flag == false)
connection = open_connection();
connection.send(data);
}
void recv_data() { /* ... */ }
} А если несколько
потоков?
48
51. Однократный вызов и отложенная инициализация
class NetFacility {
private:
connect_handle connection;
std::once_flag connection_flag;
void open_connection() {
connection = connect_manager.open(info);
}
public:
NetFacility(connect_info &_info): {}
void send_data(data_packet const &d) {
// вызывается только один раз
std::call_once(connection_flag,
&NetFacility::open_connection, this);
connection.send(data);
}
void recv_data() { /* ... */ }
}
51
52. R/W-мьютексы в С++
class Widget {
mutable std::shared_timed_mutex mut;
int data;
public:
Widget& operator=(const R& rhs) {
// эксклюзивные права на запись в *this
std::unique_lock<std::shared_timed_mutex>
lhs(mut, std::defer_lock);
// разделяемые права на чтение rhs
std::shared_lock<std::shared_timed_mutex>
rhs(other.mut, std::defer_lock);
std::lock(lhs, rhs);
// выполнить присваивание
data = rhs.data;
return *this;
}
};
52
53. R/W-мьютексы в С++
int Widget::read() {
std::shared_lock<shared_timed_mutex> lock(mut);
return val;
}
void Widget::set_value(int _val) {
std::lock_guard<shared_mutex> lock(mut);
val = _val;
}
53
54. Рекурсивные мьютексы
▪ std::recursive_mutex
▪ мьютекс можно запирать несколько раз в
одном потоке
▪ освобождать мьютекс требуется столько
раз, сколько он был захвачен
▪ использование - аналогично std::mutex
(std::lock_guard, std::unique_lock,
…)
54
55. Условные переменные
▪ std::condition_variable, std::
condition_variable_any
условная переменная, необходимо взаимодействие с
мьютексом (condition_variable) или с любым
классом (condition_variable_any), подобным
мьютексу
▪ wait - ожидание условия
▪ wait_for, wait_until - ожидание условия заданное
время или до заданного момента
▪ notify_one - сообщить одному потоку
▪ notify_all - сообщить всем потокам
55
59. Будущие результаты (future)
int thinking();
// Запуск асинхронной (“фоновой”) задачи
std::future<int> answer = std::async(thinking);
// Работа основного потока
do_other_stuff(); // в этом время работает thinking()
// Получение результатов
std::cout << "The answer is " << answer.get() << std::endl;
T1main thread
работа ожидание
T2thinking...
answer.get()
async
59
60. Будущие результаты (future)
struct Widget {
void foo(std::string const&, int);
int bar(std::string const&);
int operator()(int);
};
Widget w;
// Вызывается foo("carpe dieum", 2014) для объекта w
auto f1 = std::async(&Widget::foo, &w, "carpe diem", 2014);
// Вызывается bar("carpe dieum", 2014) для объекта tmp = w
auto f2 = std::async(&Widget::bar, w, "carpe diem");
// Вызывается tmp.operator(2014), где tmp = w
auto f3 = std::async(Widget(), 2014);
// Вызвается w(1234)
auto f4 = std::async(std::ref(w), 2014);
60
61. Будущие результаты (future)
struct Widget {
Widget();
Widget(Widget&&); // Конструктор перемещения
Widget(Widget const&) = delete; // Запретить копирование
// Оператор “перемещающее присваивание”
Widget& operator=(Widget&&);
// Запретить присваивание
Widget& operator=(Widget const&) = delete;
void foo(std::string const&, int);
int bar(std::string const&);
int operator()(int);
};
Widget w;
auto f1 = std::async(&Widget::foo, &w, "hi", 2014);
auto f2 = std::async(&Widget::bar, w, "hi");
auto f3 = std::async(Widget(), 2014);
auto f4 = std::async(std::ref(w), 2014);
61
62. Будущие результаты (future)
▪ std::launch::async - запуск функции в
асинхронном режиме
▪ std::launch::deferred - запуск в момент вызова
wait или get
▪ std::launch::async | std::launch::deferred -
на усмотрение реализации (по умолчанию)
auto f5 = std::async(std::launch::deferred,
Widget::foo(), "carpe diem", 2014);
auto f6 = std::async(std::launch::deferred,
Widget::bar(), "carpe diem");
auto f7 = std::async(std::launch::async, Widget(), 2014);
std::cout << f5.get() << std::endl; // вызывается foo()
f6.wait(); // вызывается bar()
std::cout << f7.get() << std::endl; // только ожидание
// результата 62
64. Упакованные задачи
▪ Шаблон std::packaged_task<> связывается будущий
результат (future) с функцией
▪ Вызов функции происходит при вызове объекта
packaged_task
▪ Параметр шаблона - сигнатура функции
template<>
class packaged_task<int(float, char)> {
public:
template<typename Callable>
explicit packaged_task(Callable &func);
std::future<int> get_future();
void operator()(std::vector<char>*, int);
};
пример
спецификации
шаблона для
сигнатуры
функции
int func(float,
char)
64
65. Упакованные задачи - пример (пул задач)
task
package
task
package
tasks.push_back(
std::move(task));
std::packaged_task<void()> task
= std::move(tasks.front());
batch_systemadd_task
65
task()
66. Упакованные задачи - пример
std::mutex mut;
std::deque<std::packaged_task<void()>> tasks;
bool exit_flag = false;
bool is_exit() {
std::mutex mut;
std::lock_guard<std::mutex> lock(mut);
return exit_flag;
}
void batch_system() {
while (!is_exit()) {
std::unique_lock<std::mutex> lock(mut);
if (tasks.empty()) continue;
std::packaged_task<void()> task = // получить упакованную
std::move(tasks.front()); // задачу из очереди
tasks.pop_front(); // удалить из очереди
lock.unlock();
task(); // запуск задачи
} } 66
72. “Обещанные” результаты (std::promise) - пример 1
void print_value(std::future<int>& fut) {
int x = fut.get();
std::cout << "value: " << x << std::endl;
}
int compute_value() {
std::this_thread::sleep_for(std::chrono::seconds(1));
return 42;
}
int main () {
std::promise<int> prom;
// Получаем объект future из созданного promise (обещаем)
std::future<int> fut = prom.get_future();
// Отправляем будущее значение в новый поток
std::thread th1 (print_value, std::ref(fut));
int val = compute_value();
prom.set_value(val); // Выполняем обещание
th1.join();
} 72
73. “Обещанные” результаты (std::promise) - пример 1
mainmain thread
th1print_value
prom.set_value()
print_value
th1(print_value,
std::ref(fut))
fut.get()
compute_value
работа ожидание
создание/завершение
потоков
синхронизация
73
74. “Обещанные” результаты (std::promise) - пример 2
int main() {
std::istringstream iss_numbers{"3 1 42 23 -23 93 2 -289"};
std::istringstream iss_letters{" a 23 b,e k k?a;si,ksa c"};
std::vector<int> numbers;
std::vector<char> letters;
std::promise<void> numbers_promise, letters_promise;
auto numbers_ready = numbers_promise.get_future();
auto letter_ready = letters_promise.get_future();
std::thread value_reader([&]{
std::copy(std::istream_iterator<int>{iss_numbers},
std::istream_iterator<int>{},
std::back_inserter(numbers));
numbers_promise.set_value();
std::copy_if(std::istreambuf_iterator<char>{iss_letters},
std::istreambuf_iterator<char>{},
std::back_inserter(letters), ::isalpha);
letters_promise.set_value();
}); 74
75. “Обещанные” результаты (std::promise) - пример 2
numbers_ready.wait(); // Ждать когда числа будут готовы
std::sort(numbers.begin(), numbers.end());
if (letter_ready.wait_for(std::chrono::milliseconds(100)) ==
std::future_status::timeout) {
// выводим числа, пока обрабатываются символы
for (int num : numbers) std::cout << num << ' ';
std::cout << 'n';
numbers.clear(); // Числа уже были напечатаны
}
letter_ready.wait();
std::sort(letters.begin(), letters.end());
for (char let : letters) std::cout << let << ' ';
std::cout << 'n';
// If numbers were already printed, it does nothing.
for (int num : numbers) std::cout << num << ' ';
std::cout << 'n';
value_reader.join();
}
75
76. “Обещанные” результаты (std::promise) - пример 2
mainmain
работа ожидание
value_
reader
letters_promise.
set_value()value_reader
fut.get()
iss_numbers iss_letters
number_ready.wait()
sort
letter_ready.wait_for
sort output
numbers_promise.
set_value()
создание/завершение
потоков
синхронизация
76
77. “Обещанные” результаты (std::promise), варианты
a a a a b c e i k k k s s
-289 -23 1 2 3 4 23 42 93 93
-289 -23 1 2 3 23 42 93
a a a b c e i k k k s s
77
80. Проблемы с параллелизмом на основе потоков
int doWork();
std::thread t(doWork); // 1
// или
auto fut = std::async(doWork); // 2
80
▪ Вариант, основанный на задаче (2), предпочтительней,
т.к. предполагает возвращаемое значение, которое
можно получить fut.get().
▪ Если doWork выбрасывает исключение, то get()
позволяет обработать исключения, в то время как в
первом случае выброс исключения приведёт к
завершению программы.
81. Проблемы с параллелизмом на основе потоков
81
Параллелизм задач находится на более высоком уровне
абстракции по сравнению с параллелизмом потоков,
освобождает программиста от деталей реализации:
▪ Аппаратные потоки (software threads) - те, которые
действительно выполняют вычисления (по числу ядер).
▪ Программные потоки (hardware threads) - потоки,
которые планируются ОС и выполняются на аппаратных
потоках.
▫ Легковесные потоки (lightweight threads) - потоки,
которые выполняются целиком в пространстве
пользователя.
▪ std::thread - объекты С++, которые соответствуют
определённым программным потокам, с которыми
можно выполнять операции join и detach
82. Ограниченность количества программных потоков
82
Программные потоки - ограниченный ресурс. Попытка
создать больше заданного числа потоков вызовет
исключение, даже если
int doWork() noexcept;
std::thread t(doWork); // может быть исключение!
▪ Запустить doWork в текущем потоке?
▪ Или подождать, пока освободится программный поток?
:(
83. Перегруженность аппаратных потоков (oversubscription)
83
Состояние перегруженности аппаратных потоков
oversubscription возникает, когда в системе большое
количество runnable-потоков.
Планировщик ОС выделяет программным потокам порции
(time-slice) процессорного времени.
После окончания порции происходит переключение
контекста (context switch), особенно в случае, когда поток
назначается на разные ядра:
▪ Кэш-память не загружена, большое количество
промахов по кэшу.
▪ Запуск нового потока на ядре перезаписывает записи
для старого потока, который, вероятно, будет опять
назначен на это ядро. Это опять приводит к промахам
по кэшу.
84. Перегруженность аппаратных потоков (oversubscription)
84
Выбор оптимального количества потоков для избежания
перегруженности зависит от:
▪ Момента, когда программа переходит из региона с
вводом-выводом к области с вычислениями.
▪ Стоимости переключения контекста
▪ Того, насколько эффективно потоки используют кэш
▪ Аппаратной архитектуры
85. Перегруженность аппаратных потоков (oversubscription)
85
Выбор оптимального количества потоков для избежания
перегруженности зависит от:
▪ Момента, когда программа переходит из региона с
вводом-выводом к области с вычислениями.
▪ Стоимости переключения контекста
▪ Того, насколько эффективно потоки используют кэш
▪ Аппаратной архитектуры
Сделай жизнь легче, используй std::async!
Ответственность за управление потоками
лежит на плечах разработчика стандартной
библиотеки!
86. Long live std::async!
86
std::async позволяет создать неограниченное количество
асинхронных функций
▪ Вызов std::async не гарантирует создание нового
программного потока (политики async и deferred).
▪ Асинхронная функция может быть запущена, например,
в том же потоке, где и вызывается get, wait,позволяя
избежать перегруженности (oversubscription).
▪ Возможность work-stealing (легковесных потоков)
87. Long live std::async! But...
87
std::async позволяет создать неограниченное количество
асинхронных функций
▪ Вызов std::async не гарантирует создание нового
программного потока (политики async и deferred).
▪ Асинхронная функция может быть запущена, например,
в том же потоке, где и вызывается get, wait,позволяя
избежать перегруженности (oversubscription).
▪ Возможность work-stealing (легковесных потоков)
Но std::async не универсальное средство. Недостатки:
▪ Приводит к возможному дисбалансу загрузки.
Планирование происходит на двух уровнях: ОС и
программы.
▪ Не подходит для некоторых целей (напр., GUI)
88. Когда таки нужно использовать потоки
88
▪ Нужно воспользоваться функционалом
низкоуровневой реализации потоков (например, std::
thread::native_handle)
▪ Нужно оптимизировать использвоание потоков в
программе. Допустим, если вы разрабатываете
сервер, который будет запускаться на заданной
архитектуре.
▪ Вы хотите реализовать потоки на архитектуре, где
пока нет реализации C++-concurrency.
Но это редко. В большинстве случаев смело
разрабатывайте программы на основе задач,
а не на основе потоков.
94. Помните про политику запуска задачи
94
▪ std::launch::async - функция будет запущена
асинхронного, т.е. в отдельном потоке
T1main thread
T2func
fut.get()
▪ std::launch::deferred - функция может быть
запущена только, когда вызан методв get или wait
для объекта future в потоке, вызывающем get (wait).
T1main thread
T2func
fut.get()
async
async
95. Помните про политику запуска задачи
95
▪ std::launch::async - функция будет запущена
асинхронного, т.е. в отдельном потоке
▪ std::launch::deferred - функция может быть
запущена только, когда вызан методв get или wait
для объекта future в потоке, вызывающем get (wait).
auto fut = std::async(func); // использовать политику
// запуска по умолчанию
▪ Нельзя предугадать, будет ли func выполняться
асинхронно
▪ Нельзя предугадать, будет ли func запущена на
потоке, отличном, от потока, вызывающего get (wait)
▪ Нельзя предугадать, что func будет выполнена.
96. Проблема: std::async и Thread Local Storage (TLS)
96
auto fut = std::async([](){ // Может использоваться
thread_local local_var; // TLS для независимого
... // потока
});
...
fut.get(); // а может и для этого!
Политика запуска по умолчанию конфликтует с
использованием переменных thread_local:
97. Проблема: std::async и цикла на основе wait_for
97
using namespace std::literals; // C++14 суффиксы
auto fut = std::async([]() {
std::this_thread::sleep_for(1s);
});
// Цикл, ожидающий выполнения std::async,
// может не завершиться
while (fut.wait_for(100ms) !=
std::future_status::ready)
{ /* делать что-то асинхронного */}
Использование циклов на основе вызова wait_for или
wait_until может привести к вечному ожиданию, если
задача будет запущена как отложенная (std::launch::
deferred):
98. Проблема: std::async и цикла на основе wait_for - решение
98
auto fut = std::async([]() {
std::this_thread::sleep_for(1s);
});
if (fut.wait_for(0s) ==
std::future_satus::deferred) {
fut.get(); // ожидаем результата
...
} else {
while (fut.wait_for(100ms) !=
std::future_status::ready) {
// делать какую-то работу асинхронного,
// пока ждём завершения выполнения задачи
}
// здесь fut готово
}
99. Когда использовать политику запуска по умолчанию
99
▪ Задача не требует асинхронного запуска в
отдельном потоке, отличном от вызывающего get
(wait).
▪ Не важно, thread_local-переменные какого потока
будут использоваться.
▪ Или есть гарантия, что get (wait) будут вызваны для
объекта future, возвращённого std::async, или
задача может быть вовсе на запущена.
▪ При использовании wait_for или wait_until
допускается возможность отложенного запуска
задачи.
Если какие-либо пункты не выполняются, лучше
гарантировать асинхронный запуск задачи через
передачу std::launch::async
101. Joinable и unjoinable
101
Объект std::thread может пребывать в двух состояниях:
▪ joinable: объект соответствует потоку, который
выполняется или может быть запущен.
▪ unjoinable: объект, с которым нельзя выполнить
операцию join:
▫ выполнен конструктур по умолчанию для std::
thread, т.е. std::thread может не имеет функции
для выполнения и поэтому
не соответствует реальному потоку.
▫ std::thread, который был перемещён (moved)
▫ std::thread, который был присоединён (joined)
▫ std::thread, который был отсоденинён (detached)
102. Проблема с joinable-потоками
102
constexpr auto n = 10'000'000; // C++14-style
bool doWork(std::function<bool(int)> pred, // условие
int maxVal = n) {
std::vector<int> goodVals; // значения, удовл. условию
std::thread t([&pred, maxVals, &goodVals]{
for (auto i = 0; i <= maxVals; i++) {
if (pred(i)) goodVals.push_back(i); }
});
auto nh = t.native_handle();
... // низкоуровневые манипуляции с потоком
if (conditionAreSatisfied()) {
t.join();
performComputation(goodVals);
return true;
}
return false;
}
103. Проблема с joinable-потоками
103
constexpr auto n = 10'000'000; // C++14-style
bool doWork(std::function<bool(int)> pred, // условие
int maxVal = n) {
std::vector<int> goodVals; // значения, удовл. условию
std::thread t([&pred, maxVals, &goodVals]{
for (auto i = 0; i <= maxVals; i++) {
if (pred(i)) goodVals.push_back(i); }
});
auto nh = t.native_handle();
...
if (conditionAreSatisfied()) {
t.join();
performComputation(goodVals);
return true;
}
return false;
}
sched_param sch;
int policy;
pthread_getschedparam(nh, &policy, &sch);
sch.sched_priority = 20;
if (pthread_setschedparam(nh, SCHED_FIFO, &sch)) {
std::cout << "Failed to setschedparam: "
<< std::strerror(errno) << 'n';
}
104. Проблема с joinable-потоками
constexpr auto n = 10'000'000; // C++14-style
bool doWork(std::function<bool(int)> pred, // условие
int maxVal = n) {
std::vector<int> goodVals; // значения, удовл. условию
std::thread t([&pred, maxVals, &goodVals]{
for (auto i = 0; i <= maxVals; i++) {
if (pred(i)) goodVals.push_back(i); }
});
auto nh = t.native_handle();
...
if (conditionAreSatisfied()) {
t.join();
performComputation(goodVals);
return true; // ok, т.к. был t.join()
}
return false; // не было t.join()! выброс исключения
} // и аварийное завершение программы 104
105. Проблема с joinable-потоками
Как можно решить проблему (наивно):
▪ Неявный join. Деструктор std::thread будет ожидать
завершения потока. Но это приведёт к неочевидному
коду, например, когда поток ждёт завершения doWork,
уже зная, что условие не выполнено.
▪ Неявный detach. Деструктор разрывает связь между
std::thread и потоком выполнения. Поток
продолжает работать. В этом случае, например, при
завершении функции doWork, поток продолжает
работать. Этот поток может использовать
автоматические переменные из стека doWork.
Поэтому стандарт запретил уничтожение потока в
сотоянии joinable: деструктур такого объекта вызывает
завершение программы.
105
106. Проблема с joinable-потоками
106
▪ Программист должен следить за тем, что объект
должен находиться в состоянии unjoinable вне его
области видимости.
▪ Обеспечение этого требования - задача непростая,
поскольку требует отслеживания всех выходов из
функции через return, continue, break, goto,
exception.
▪ Необходимо обеспечить выполнение определённого
действия каждый раз при выходе из блока.
107. Make it RAII!
▪ Программист должен следить за тем, что объект
должен находиться в состоянии unjoinable вне его
области видимости.
▪ Обеспечение этого требования - задача непростая,
поскольку требует отслеживания всех выходов из
функции через return, continue, break, goto,
exception.
▪ Необходимо обеспечить выполнение определённого
действия каждый раз при выходе из блока.
Решение: RAII-объекты (Resouce Acquisition Is
Initialization), (к которым относятся std::unique_ptr,
std::shared_ptr, std::lock_guard, std::fstream и
др.), деструктор которых содержит необходимое действие.
107
108. RAII
108
class ThreadRAII {
public:
enum class DestrAction { join, detach };
ThreadRAII(std::thread&& t, DestrAction a):
action{a}, t{std::move(t)} { }
~ThreadRAII() { // действие выполняется в деструкторе
if (t.joinable()) {
if (action == DestrAction::join) {
t.join();
} else {
t.detach();
}
}
}
std::thread& get() { return t; }
private:
DestrAction action; // action in destuctor
std::thread t;
};
109. RAII
109
bool doWork(std::function<bool(int)> pred, int maxVal = n) {
std::vector<int> goodVals; // значения, удовл. условию
ThreadRAII t{ // использовать RAII-объект
std::thread([&stencil, maxVals, &goodVals]{
for (auto i = 0; i <= maxVals; i++) {
if (pred(i)) goodVals.push_back(i); }
}), ThreadRAII::DestrAction::join // действие
}; // в деструкторе
auto nh = t.get().native_handle();
...
if (conditionAreSatisfied()) {
t.get().join();
performComputation(goodVals);
return true;
}
return false;
}
112. Недостатки синхр-ции на основе условных переменных
std::mutex mut;
std::queue<Widget> widget_queue;
std::condition_variable cond;
void producer() {
for (;;) {
Widget const w = get_request();
std::lock_guard<std::mutex> lock(mut);
widget_queue.push(data);
cond.notify_one();
} }
void consumer() {
for (;;) {
std::unique_lock<std::mutex> lock(mut);
cond.wait(lock, []{return !widget_queue.empty();});
Widget w = widget_queue.pop();
lock.unlock();
process(widget);
} }
112
113. Недостатки синхр-ции на основе условных переменных
▪ Необходимость использования мьютекса
std::unique_lock<std::mutex> lock(mut);
cond.wait(lock, ...);
А что, если потоки выполняют код, который не нуждается в
блокировке мьютекса? Например, один поток инициализирует
структуру, после чего сообщает другому, что структура готова.
▪ Пропущенный сигнал
Поток может отправить сигнал (notify_one/all) тогда, когда другой
поток ещё не начал его ожидать.
▪ Ложное пробуждение (spurious wakeup)
Поток может проснуться тогда, когда сигнал не был отправлен Или
когда он был отправлен потока, а затем условие перестало
выполняться. Поэтому нужна дополнительная проверка:
cond.wait(lock, []{return !widget_queue.empty();}));
А что, если поток не может проверить условие?!
113
114. Недостатки синхр-ции на основе условных переменных
std::atomic<bool> flag(false);
...
flag = true;
...
while (!flag); // активное ожидание! :(
...
114
Для решения проблемы ложного пробуждения можно использовать
атомарный флаг:
Или так:
{
flag = true;
cv.notify_one();
}
{
cv.wait(lk, [] { return flag; }); // код “с запашком” :(
}
116. “Обещанные” результаты (std::promise)
std::futurestd::promise<...> p
p.get_future().wait()
p.set_value()
116
ok, let’s move!
▪ Не требует мьютексов
▪ Не нуждается в атомарных флагах
▪ Не использует активного ожидания
▪ Не зависит от порядка выполнения wait, set_value
117. “Обещанные” результаты (std::promise)
std::futurestd::promise<...> p
p.get_future().wait()
p.set_value()
117
ok, let’s move!
▪ Не требует мьютексов
▪ Не нуждается в атомарных флагах
▪ Не использует активного ожидания
▪ Не зависит от порядка выполнения wait, set_value
▪ Надо заботиться о поведении деструктора future
▪ Можно отправить сигнал только один раз
118. “Обещанные” результаты (std::promise) - пример
std::promise<void> p;
void react(); // реакция на условие
void detect() { // обнаружение условия
std::thread t([] {
p.get_future().wait();
react();
});
// делаем что-то // в это время t спит
p.set_value(); // разбудить t
// делаем ещё что-то
t.join();
};
118
119. “Обещанные” результаты (std::promise) - пример
std::promise<void> p;
void react(); // реакция на условие
void detect() { // обнаружение условия
std::thread t([] {
p.get_future().wait();
react();
});
// а что, если здесь возникнет исключение??
p.set_value(); // разбудить t
// делаем ещё что-то
t.join();
};
119
120. “Обещанные” результаты (std::promise) - пример
std::promise<void> p;
void react(); // реакция на условие
void detect() { // обнаружение условия
ThreadRAII t(std::thread([] {
p.get_future().wait();
react();
)});
...
p.set_value(); // разбудить t
// делаем ещё что-то
t.join();
};
120
121. Множественная отправка сигналов
std::promise<void> p;
void detect() {
auto sf = p.get_future().share();
std::vector<std::thread> vt;
for (auto i = 0; i < threadsToRun; i++) {
vt.emplace_back([sf]{ sf.wait();
react(); });
}
// ...
p.set_value();
// ...
for (auto &t: vt) t.join();
};
121
122. Множественная отправка сигналов
std::promise<void> p;
void detect() {
auto sf = p.get_future().share();
std::vector<std::thread> vt;
for (auto i = 0; i < threadsToRun; i++) {
vt.emplace_back([sf]{ sf.wait();
react(); });
}
// ...
p.set_value();
// ...
for (auto &t: vt) t.join();
};
// RAII... 122
123. Множественная отправка сигналов
std::promise<void> p;
void detect() {
auto sf = p.get_future().share();
std::vector<ThreadRAII> vt;
for (auto i = 0; i < nthreads; i++) {
vt.emplace_back(std::move(ThreadRAII{
std::thread([sf]{
sf.wait();
react(); }
), ThreadRAII::DestrAction::join
}));
}
// ...
p.set_value();
};
123
124. Разделяемые будущие результаты shared_future
int main() {
std::promise<void> ready_promise, t1_ready_promise,
t2_ready_promise;
std::shared_future<void>
ready_future(ready_promise.get_future());
std::chrono::time_point<std::chrono::high_resolution_clock>
start;
auto fun1 = [&]() -> std::chrono::duration<double, std::milli>
{
t1_ready_promise.set_value();
ready_future.wait(); // ожидать сигнала из main()
return std::chrono::high_resolution_clock::now() - start;
};
auto fun2 = [&]() -> std::chrono::duration<double, std::milli>
{
t2_ready_promise.set_value();
ready_future.wait(); // ожидать сигнала из main()
return std::chrono::high_resolution_clock::now() - start;
}; 124
125. Разделяемые будущие результаты shared_future
auto result1 = std::async(std::launch::async, fun1);
auto result2 = std::async(std::launch::async, fun2);
// ждать, пока потоки не будут готовы
t1_ready_promise.get_future().wait();
t2_ready_promise.get_future().wait();
// потоки готовы - начать отчёт времени
start = std::chrono::high_resolution_clock::now();
// запустить потоки
ready_promise.set_value();
std::cout << "Thread 1 received the signal "
<< result1.get().count() << " ms after startn"
<< "Thread 2 received the signal "
<< result2.get().count() << " ms after startn";
}
125
126. Разделяемые будущие результаты shared_future
main
работа ожидание
создание/завершение
потоков
синхронизация
T1
T2
t2_ready.
set_value
start
return
return
output
ready.
set_value
t1_ready.
set_value
126
128. Хранение результата для future
Деструктор объекта future ведёт себя иногда так, как
будто он выполняет неявный join, а в некоторых случае -
как будто выполняет неявный detach.
128
Вызываемый
поток
Вызывающий
поток
future std::promise
Где хранится результат вызывающего потока?
Вызывающий поток может завершиться до того, как
вызываемый выполнит fut.get(), и результат не может
храниться в объекте std::promise вызываемого потока.
Объект future не может быть хранилищем для результата,
т.к. он может быть скопирован в объекты shared_future,
после чего возникает вопрос, какая из копий соответствует
результату?
129. Два варианта поведения деструктора future
129
Вызываемый
поток
Вызывающий
поток
future std::promise
Результат
вызывающего
Поведение деструктора future зависит от разделяемого
состояния (shared state):
▪ Деструктор последнего объекта future,
указывающего на разделяемое состояние (shared
state) для какой-то асинхронной задачи, блокируется
до завершения выполнения этой задачи, т.е. выполняет
“join”.
▪ Деструкторы всех других объектов future просто
уничтожают объект future. Это аналогично вызову
detach для потока.
shared state
130. Два варианта поведения деструктора future
130
▪ Деструктор последнего объекта future, указывающего
на разделяемое состояние, выполняет “join”, если:
▫ объект указывает на разделяемое состояние,
созданное std::async
▫ задача, породившая future, была запущена
асинхронно
▫ future - это последний объект future, указывающий
на разделяемое состояние
Зачем это нужно?
▪ Чтобы избежать неявного вызова detach для потока, в
котором выполняется задача.
▪ Срабатывание деструктора не должно приводить к
завершению программы (попытка компромиса)
131. Два варианта поведения деструктора future
131
// Деструктор futs может блокироваться
std::vector<std::future<void>> futs;
// Объект может блокироваться при уничтожении
class Widget {
private:
std::shared_future<double> fut;
};
132. Два варианта поведения деструктора future - пример
132
auto fut1 = std::async(std::launch::async, [] {
std::this_thread::sleep_for(1s);
std::cout << "1st task finishedn";
});
auto fut2 = std::async(std::launch::async, [](auto fut2) {
return "2nd task finishedn";
}, std::move(fut1));
std::cout << fut2.get();
137. Неблокирующие будущие результаты (then)
auto func1() {
std::cout << "begin thinking over the answer...n";
std::this_thread::sleep_for(dur3);
return 40;
}
auto func2(int x) {
std::cout << "continue thinking over the answer...n";
std::this_thread::sleep_for(dur1);
return x + 2;
}
auto func3(int x) {
std::cout << "still thinking...n";
std::this_thread::sleep_for(dur2);
return "number " + std::to_string(x);
}
void do_some_stuff() { std::cout << "do some useful stuff"; }
void do_some_other_stuff() { std::cout << "do other stuff"; }137
138. Неблокирующие будущие результаты (then)
int main() {
auto f1 = std::async(func1);
auto f2 = std::async(func2, f1.get());
auto f3 = std::async(func3, f2.get());
std::cout << "waiting for the answer...n";
do_some_stuff();
std::cout << "answer: " << f3.get() << std::endl;
do_some_other_stuff();
138
139. Неблокирующие будущие результаты (then)
int main() {
auto f1 = std::async(func1);
auto f2 = std::async(func2, f1.get());
auto f3 = std::async(func3, f2.get());
std::cout << "waiting for the answer...n";
do_some_stuff();
std::cout << "answer: " << f3.get() << std::endl;
do_some_other_stuff();
Каждый раз после получения результата выполняется
создание новой асинхронной задачи.
Поток может быть заблокирован при вызове get() для
ожидания результата.
139
140. Неблокирующие будущие результаты (then)
$ ./prog
begin thinking over the answer...
continue thinking over the answer...
waiting for the answer...
do some useful stuff
answer: still thinking...
number 42
do some other useful stuff
140
141. Неблокирующие будущие результаты (then)
#define BOOST_THREAD_PROVIDES_FUTURE
#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION
#include <boost/thread/future.hpp>
int main() {
auto f = boost::async([](){
return func1();
});
do_some_stuff();
f.then([](auto f){
std::cout << "answer: " << f.get() << std::endl;
});
do_some_other_stuff();
141
вызывающий поток блокируется
142. Неблокирующие будущие результаты (then)
#define BOOST_THREAD_PROVIDES_FUTURE
#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION
#include <boost/thread/future.hpp>
int main() {
auto f = boost::async([](){
return func1();
}).then([](auto f){
return func2(f.get());
}).then([](auto f){
return func3(f.get());
});
std::cout << "waiting for the answer...n";
do_some_stuff();
f.then([](auto f){
std::cout << "answer: " << f.get() << std::endl;
});
do_some_other_stuff(); 142
143. Неблокирующие будущие результаты (then)
#define BOOST_THREAD_PROVIDES_FUTURE
#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION
#include <boost/thread/future.hpp>
int main() {
auto f = boost::async([](){
return func1();
}).then([](auto f){
return func2(f.get());
}).then([](auto f){
return func3(f.get());
});
std::cout << "waiting for the answer...n";
do_some_stuff();
f.then([](auto f){
std::cout << "answer: " << f.get() << std::endl;
});
do_some_other_stuff();
вызывающий поток не блокируется
143
144. Неблокирующие будущие результаты (then)
#define BOOST_THREAD_PROVIDES_FUTURE
#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION
#include <boost/thread/future.hpp>
int main() {
auto f = boost::async([](){
return func1();
}).then([](auto f){
return func2(f.get());
}).then([](auto f){
return func3(f.get());
});
std::cout << "waiting for the answer...n";
do_some_stuff();
f.then([](auto f){
std::cout << "answer: " << f.get() << std::endl;
}).wait();
do_some_other_stuff();
вызывающий поток блокируется
144
145. Неблокирующие будущие результаты (then)
$ g++ -Wall -pedantic -pthread -lboost_system
-lboost_thread -std=c++14 -O2 prog.cpp -o prog
$ ./prog
waiting for the answer...
do some useful stuff
begin thinking over the answer...
continue thinking over the answer...
still thinking...
answer: number 42
do some other useful stuff
145
146. Неблокирующие будущие результаты (then)
Блокирующие future Неблокирующие future
f2
f3
f1
f
▪ устанавливается явный
порядок выполнения
▪ нет блокировок
▪ поток один
▪ порядок выполнения
неопределён
▪ возможны блокировки
▪ для каждой задачи
создаётся отдельный поток 146
147. Ожидание выполнения всех задач (when_all)
f2
f1
f3
Будущий результат f4 зависит от выполнения всех
будущих результатов f1, f2, f3 и начинает выполняться
после завершения выполнения задач, им соответствующих
(подобно барьерной синхронизации).
f4
147
148. Ожидание выполнения всех задач (when_all)
#define BOOST_THREAD_PROVIDES_FUTURE_WHEN_ALL_WHEN_ANY
#include <boost/thread/future.hpp>
std::vector<boost::future<void>> task_chunk;
task_chunk.emplace_back(boost::async([]()
{ std::cout << "hello from task 1n"; }));
task_chunk.emplace_back(boost::async([]()
{ std::cout << "hello from task 2n"; }));
task_chunk.emplace_back(boost::async([]()
{ std::cout << "hello from task 3n"; }));
auto join_task = boost::when_all(task_chunk.begin(),
task_chunk.end());
do_some_stuff();
join_task.wait();
148
149. Ожидание выполнения всех задач (when_all)
std::vector<boost::future<int>> task_chunk;
task_chunk.emplace_back(boost::async(boost::launch::async,
[](){ std::cout << "hello from task 1n"; return 10; }));
task_chunk.emplace_back(boost::async(boost::launch::async,
[](){ std::cout << "hello from task 2n"; return 20; }));
task_chunk.emplace_back(boost::async(boost::launch::async,
[](){ std::cout << "hello from task 3n"; return 12; }));
auto join_task = boost::when_all(task_chunk.begin(),
task_chunk.end())
.then([](auto results){
auto res = 0;
for (auto &elem: results.get())
res += elem.get();
return res;
});
do_some_stuff();
std::cout << "result: " << join_task.get() << std::endl;
join_task
имеет тип
future<
vector<
future<T>>>
149
151. Ожидание выполнения всех задач (when_all)
$ g++ -Wall -pedantic -pthread -lboost_system
-lboost_thread -std=c++14 -O2 prog.cpp -o prog
$ ./prog
hello from task 1
hello from task 3
hello from task 2
do some useful stuff
result: 42
151
152. Ожидание выполнения какой-либо задачи (when_any)
f2
f1
f3
Будущий результат f4 зависит от выполнения одного из
будущих результатов f1, f2, f3 и начинает выполняться
после завершения выполнения хотя бы одной задачи
(подобно синхронизации “эврика”).
f4
152
153. Ожидание выполнения какой-либо задачи (when_any)
std::vector<boost::future<decltype(M_PI)>> task_chunk;
task_chunk.emplace_back(boost::async(boost::launch::async,
[]() { std::this_thread::sleep_for(dur1); return M_PI; }));
task_chunk.emplace_back(boost::async(boost::launch::async,
[]() { std::this_thread::sleep_for(dur2); return M_E; }));
task_chunk.emplace_back(boost::async(boost::launch::async,
[]() { std::this_thread::sleep_for(dur3); return M_LN2; }));
auto join_task = boost::when_any(task_chunk.begin(),
task_chunk.end())
.then([](auto results) {
for (auto &elem: results.get()) {
if (elem.is_ready()) { return elem.get(); }
}
exit(1); // this will never happen
});
do_some_stuff();
std::cout << "result: " << join_task.get() << std::endl;
153
join_task
имеет тип
future<
vector<
future<T>>>
154. Ожидание выполнения какой-либо задачи (when_any)
$ g++ -Wall -pedantic -pthread -lboost_system
-lboost_thread -std=c++14 -O2 prog.cpp -o prog
do some useful stuff
result: 2.71828
do some useful stuff
result: 0.693147
do some useful stuff
result: 3.14159
154
Возможные варианты:
158. Мьютексы и очереди задач
Блокировка мьютекса
▪ потоки блокируются
▪ имеется возможность
дедлока
▪ небольшая
масштабируемость
▪ порядок следования
сообщения в логе отличается
от последовательности
поступления
Очереди задач
▪ потоки не блокируются
▪ отсутствует возможность
дедлока
▪ высокая масштабируемость
▪ порядок следования
сообщения в логе совпадает
с фактическим
158
159. Паттерн: потокобезопасная обёртка над данными
Требования к потокобезопасным “обёрткам”:
1. Сохранение интерфейса
widget w; => w.func("hi folks!");
wrapper<widget> w; => w.func("hi folks!");
2. Универсальность. Заранее может быть неизвестны
методы, которые необходимо обернуть. Некоторые методы
сложно обернуть: конструкторы, операторы, шаблоны и т.д.
3. Поддержка транзакций
account.deposit(Sergey, 1000)
account.withdraw(Ivan, 1000);
log.print("user ", username, "data ");
log.print("time ", logmsg);
Реализация отдельных методов может не обеспечить
необходимую гранулярность.
159
160. Паттерн: обёртка над данными с блокировками
template<typename T>
class wrapper {
private:
T t; // оборачиваемый объект
... // состояние враппера
public:
monitor(T _t): t(_t) { }
template <typename F>
// 1. получаем любую функцию
// 2. подставляем в неё оборачиваемый объект
// 3. выполняем и возвращаем результат
auto operator()(F f) -> decltype(f(t)) {
// работа враппера
auto ret = f(t);
// ...
return ret;
}
}; 160
161. Потокобезопасная обёртка над данными с блокировками
template<typename T>
class monitor {
private:
T t;
std::mutex m;
public:
monitor(T _t): t(_t) { }
template <typename F>
auto operator()(F f) -> decltype(f(t)) {
std::lock_guard<std::mutex> lock(m);
// вызов “объявления” под защитой мьютекса
return f(t);
}
};
161
162. Потокобезопасная обёртка над данными с блокировками
monitor<std::string> smon{"start "}; // инициализация
std::vector<std::future<void>> v;
for (auto i = 0; i < 5; i++) { // выполнить асинхр. задачи...
v.emplace_back(std::async(std::launch::async, [&, i]{
smon([=](auto &s){ // "объявление" функции
s += "i = " + std::to_string(i);
s += " ";
});
smon([](auto &s){ // "объявление" функции
std::cout << s << std::endl;
});
}));
}
for (auto &f: v) // дождаться завершения
f.wait();
std::cout << "donen";
162
163. Потокобезопасная обёртка над данными с блокировками
start i = 1
start i = 1 i = 0
start i = 1 i = 0 i = 2
start i = 1 i = 0 i = 2 i = 4
start i = 1 i = 0 i = 2 i = 4 i = 3
done
start i = 0
start i = 0 i = 2
start i = 0 i = 2 i = 3
start i = 0 i = 2 i = 3 i = 1
start i = 0 i = 2 i = 3 i = 1 i = 4
done
start i = 0
start i = 0 i = 2
start i = 0 i = 2 i = 4
start i = 0 i = 2 i = 4 i = 1
start i = 0 i = 2 i = 4 i = 1 i = 3
done 163
164. Потокобезопасная обёртка над данными с блокировками
monitor<std::ostream&> mon_cout{std::cout};
std::vector<std::future<void>> v;
for (auto i = 0; i < 5; i++) {
v.emplace_back(std::async(std::launch::async, [&, i]{
mon_cout([=](auto &cout){
cout << "i = " << std::to_string(i);
cout << "n";
});
mon_cout([=](auto &cout){
cout << "hi from " << i << std::endl;
});
}));
}
for (auto &f: v)
f.wait();
mon_cout([](auto &cout){
cout << "donen";
}); 164
165. Потокобезопасная обёртка над данными с блокировками
i = 0
i = 2
hi from 2
hi from 0
i = 1
hi from 1
i = 3
hi from 3
i = 4
hi from 4
done
i = 0
i = 3
i = 2
hi from 2
i = 1
hi from 1
hi from 3
i = 4
hi from 4
hi from 0
done
i = 0
hi from 0
i = 4
hi from 4
i = 2
hi from 2
i = 3
hi from 3
i = 1
hi from 1
done
i = 0
hi from 0
i = 2
hi from 2
i = 3
hi from 3
i = 1
hi from 1
i = 4
hi from 4
done
165
166. Потокобезопасная обёртка над данными на основе очереди задач
template<typename T> class concurrent {
private: // потокобезопасная очередь
T t;
concurrent_queue<std::function<void()>> q;
bool done = false;
std::thread thd;
public:
concurrent(T t_): t{t_}, thd{[=]{
while (!done) {
(*q.wait_and_pop())(); // дождаться поступления
} // значения, извлечь
} } { } // из очереди и выполнить
~concurrent()
{ q.push([=]{ done = true; });
thd.join(); }
template<typename F> void operator()(F f)
{ q.push([=]{ f(t); }); }
}; 166
167. Потокобезопасная обёртка над данными на основе очереди задач
concurrent<std::string> smon{"start "}; // инициализация
std::vector<std::future<void>> v;
for (auto i = 0; i < 5; i++) { // выполнить асинхр. задачи...
v.emplace_back(std::async(std::launch::async, [&, i]{
smon([=](auto &s){ // "объявление" функции
s += "i = " + std::to_string(i);
s += " ";
});
smon([](auto &s){ // "объявление" функции
std::cout << s << std::endl;
});
}));
}
for (auto &f: v) // дождаться завершения
f.wait();
std::cout << "donen";
167
168. Потокобезопасная обёртка над данными на основе очереди задач
start i = 0
start i = 0 i = 2
start i = 0 i = 2 i = 3
start i = 0 i = 2 i = 3 i = 1
start i = 0 i = 2 i = 3 i = 1 i = 4
done
start i = 0
done
start i = 0 i = 2
start i = 0 i = 2 i = 1
start i = 0 i = 2 i = 1 i = 3
start i = 0 i = 2 i = 1 i = 3 i = 4
start i = 0
start i = 0 i = 1
start i = 0 i = 1 i = 4
start i = 0 i = 1 i = 4 i = 3
start i = 0 i = 1 i = 4 i = 3 i = 2
done 168
169. Потокобезопасная обёртка над данными на основе очереди задач
concurrent<std::string> smon{"start "}; // инициализация
std::vector<std::future<void>> v;
for (auto i = 0; i < 5; i++) { // выполнить асинхр. задачи...
v.emplace_back(std::async(std::launch::deferred, [&, i]{
smon([=](auto &s){ // "объявление" функции
s += "i = " + std::to_string(i);
s += " ";
});
smon([](auto &s){ // "объявление" функции
std::cout << s << std::endl;
});
}));
}
for (auto &f: v) // дождаться завершения
f.wait();
std::cout << "donen";
169
start i = 0
start i = 0 i = 1
start i = 0 i = 1 i = 2
start i = 0 i = 1 i = 2 i = 3
start i = 0 i = 1 i = 2 i = 3 i = 4
done
170. Потокобезопасная обёртка над данными на основе очереди задач
concurrent<std::ostream&> mon_cout{std::cout};
std::vector<std::future<void>> v;
for (auto i = 0; i < 5; i++) {
v.emplace_back(std::async(std::launch::async, [&, i]{
mon_cout([=](auto &cout){
cout << "i = " << std::to_string(i);
cout << "n";
});
mon_cout([=](auto &cout){
cout << "hi from " << i << std::endl;
});
}));
}
for (auto &f: v)
f.wait();
mon_cout([](auto &cout){
cout << "donen";
}); 170
171. Потокобезопасная обёртка над данными на основе очереди задач
i = 0
hi from 0
i = 2
hi from 2
i = 3
hi from 3
i = 4
hi from 4
i = 1
hi from 1
done
i = 0
hi from 0
i = 2
hi from 2
i = 3
hi from 3
i = 4
hi from 4
i = 1
hi from 1
done
i = 0
i = 1
hi from 1
hi from 0
i = 2
hi from 2
i = 4
hi from 4
i = 3
hi from 3
done
i = 0
hi from 0
i = 2
hi from 2
i = 1
i = 3
hi from 3
hi from 1
i = 4
hi from 4
done
171
172. Потокобезопасная обёртка над данными на основе очереди задач
template<typename F>
auto operator()(F f) -> std::future<decltype(f(t))> {
// создаём объект promise (shared_ptr<promise>)
auto p = std::make_shared<std::promise<decltype(f(t))>>();
// получаем из promise объект future
auto ret = p->get_future();
q.push([=]{
// выполняем обещание уже внутри потока
try { p->set_value(f(t)); }
catch (...)
{ p->set_exception(std::current_exception()); }
});
return ret;
}
Данная версия operator() позволяет вернуть значение при вызове
функции:
172
173. Потокобезопасная обёртка над данными на основе очереди задач
template<typename F>
auto operator()(F f) -> std::future<decltype(f(t))> {
auto p = std::make_shared<std::promise<decltype(f(t))>>();
auto ret = p->get_future();
q.push([=]{
try { set_value(*p, f, t); }
catch (...)
{ p->set_exception(std::current_exception()); }
});
return ret;
}
template<typename Fut, typename F, typename T1>
void set_value(std::promise<Fut>& p, F& f, T1& t)
{ p.set_value(f(t)); }
template<typename F, typename T1>
void set_value(std::promise<void>& p, F& f, T1& t)
{ f(t); p.set_value(); }
173
174. Потокобезопасная обёртка над данными на основе очереди задач
template<typename F>
auto operator()(F f) -> std::future<decltype(f(t))> {
auto p = std::make_shared<std::promise<decltype(f(t))>>();
auto ret = p->get_future();
q.push([=]{
try { set_value(*p, f, t); }
catch (...)
{ p->set_exception(std::current_exception()); }
});
return ret;
}
auto f = smon([](auto &s){
s += "donen";
std::cout << s;
return s;
});
std::cout << "return value: " << f.get() << std::endl; 174
175. Мьютексы и очереди задач
Блокировка мьютекса
▪ потоки блокируются
▪ имеется возможность
дедлока
▪ небольшая
масштабируемость
▪ порядок следования
сообщения в логе отличается
от последовательности
поступления
Очереди задач
▪ потоки не блокируются
▪ отсутствует возможность
дедлока
▪ высокая масштабируемость
▪ порядок следования
сообщения в логе совпадает
с фактическим
175
176. Потокобезопасная обёртка на основе очереди задач - применение
class backgrounder {
public:
future<bool> save(std::string file) {
c([=](data& d) {
... // каждая функция - в отдельной транзакции
});
}
future<size_t> print(some& stuff) {
c([=, &stuff](data& d) {
... // атомарный неделимый вывод
});
}
private:
struct data { /* ... */ } // данные
concurrent<data> c; // обёртка для потокобезопасного
}; // выполнения операций с данными
176