ЛЕКЦИЯ 7. Многопоточное программирование без блокировок. Модель потребитель-производитель. Потокобезопасный стек: проблема ABA, указатели опасности, сборщики мусора, счётчик ссылок, применение модели памяти С++.
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ЛЕКЦИЯ 8. Многопоточное программирование без использования блокировок. Модель потребитель-производитель. Потокобезопасный стек. Проблема ABA. Указатели опасности.
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...Alexey Paznikov
ЛЕКЦИЯ 6. Разработка параллельных структур данных на основе блокировок
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ЛЕКЦИЯ 0. Описание курса. Общие вопросы, структура курса, требования. Содержание курса. Полезные ресурсы
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ЛЕКЦИЯ 5. Шаблоны многопоточного программирования
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...Alexey Paznikov
ЛЕКЦИЯ 6. Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель памяти C++
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ЛЕКЦИЯ 4. Шаблоны многопоточного программирования
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
Доклад Кулагина И.И., Пазникова А.А., Курносова М.Г. "Оптимизация информационных обменов в параллельных PGAS-программах" на 3-й Всероссийской научно-технической конференции «Суперкомпьютерные технологии» (СКТ-2014)
29 сентября – 4 октября 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
ЛЕКЦИЯ 0. Описание курса. Общие вопросы, структура курса, требования. Содержание курса. Полезные ресурсы
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ЛЕКЦИЯ 5. Шаблоны многопоточного программирования
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...Alexey Paznikov
ЛЕКЦИЯ 6. Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель памяти C++
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ЛЕКЦИЯ 4. Шаблоны многопоточного программирования
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
Доклад Кулагина И.И., Пазникова А.А., Курносова М.Г. "Оптимизация информационных обменов в параллельных PGAS-программах" на 3-й Всероссийской научно-технической конференции «Суперкомпьютерные технологии» (СКТ-2014)
29 сентября – 4 октября 2014 г., с. Дивноморское
ЛЕКЦИЯ 3. Реентерабельность. Сигналы. Локальные данные потоков. Принудительное завершение потоков
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...Alexey Paznikov
ЛЕКЦИЯ 7. Модель памяти С++. Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...Alexey Paznikov
ЛЕКЦИЯ 2. POSIX Threads. Жизненный цикл потоков. Планирование. Синхронизация
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...Alexey Paznikov
ЛЕКЦИЯ 5. Многопоточное программирование в языке С++. Работа с потоками. Защита данных. Синхронизация. Будущие результаты
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ЛЕКЦИЯ 4. Стандарт POSIX Threads. Реентерабельность функций. Обработка сигналов. Локальные данные потоков. Принудительное завершение потоков. Шаблоны программирования с использованием потоков
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMSergey Platonov
Зачастую, знакомство с алиасингом в C++ у многих программистов начинается и заканчивается одинаково: -fno-strict-aliasing. На вопросы новичка, более опытные коллеги отвечают в стиле: «не трогай! а то все сломаешь!». Новичок и не трогает. В докладе будет предпринята попытка заглянуть под капот и понять, что же там, внутри. Что такое алиасинг, где он может быть полезен и какие реальные преимущества дает. Тема будет рассмотрена и со стороны программиста и со стороны разработчика компилятора. А по сему, вопрос «зачем?» будет центральным в повествовании.
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Sergey Platonov
В последнее время в промышленной разработке ПО особую популярность обретают Domain-Specific Lanugages (DSL). Они драматически упрощают разработку и дают возможность “программировать” не только программистам, но и пользователям прикладных программ.
В своем докладе я расскажу об опыте использования DSL применительно к С++, причем упор будет сделан на производительность кода DSL, и его мгновенную “встраиваемость” в запущенную программу путем компиляции DSL-кода в нативный код с помощью инструментария LLVM.
Модель памяти C++ - Андрей Янковский, ЯндексYandex
В докладе Андрей расскажет о моделях памяти различных процессоров, о тонкостях реализации неблокирующих алгоритмов и о том, какое отношение всё это имеет к С++.
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловSergey Platonov
В докладе перед нами откроется великолепный мир велосипедов и устаревших технологий, которые люди продолжают переносить в новые проекты и повсеместно использовать. Мы поговорим о:
Copy-On-Write
разработке без оглядки на готовые решения и к чему это приводит
force inline
оптимизациях, которые отлично себя показывают на бенчмарках и плохо себя ведут в реальной жизни
бездумно отключаемых оптимизациях компилятора
тонкостях стандартной библиотеки для повседневного использования
супер качественном велосипедостроении
Использование юнит-тестов для повышения качества разработкиvictor-yastrebov
В докладе рассмотрены подходы к созданию надежных юнит-тестов, которые просты в поддержке и модернизации, а также принципы создания кода пригодного для покрытия автотестами. Приведены два способа внедрения зависимости: с использованием конструктора тестируемого объекта, а также с использованием подхода "выделить и переопределить". Каждый из способов разобран на примере, демонстрирующем особенности его реализации и применения. Приведен ряд практических советов, нацеленных на создание надежных юнит-тестов. Использование на практике приведенных подходов и принципов позволяет упростить процесс поддержки и модификации существующего кода, а также дает уверенность в надежности работы добавляемого нового функционала. В конечном итоге это приводит к повышению качества разрабатываемого продукта.
Догнать и перегнать boost::lexical_castRoman Orlov
Разбор нестандартной реализации преобразования целого числа в строку без использования циклов и рекурсивных вызовов времени исполнения - только рекурсия на этапе компиляции
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Yandex
Основываясь на опыте разработки Крипты, Дмитрий рассмотрит средства реализации статического и динамического полиморфизма в C++, а также некоторые их паттерны и антипаттерны.
Доклад вводит в рассмотрение универсальный адаптер, позволяющий обернуть любой класс с целью добавления новых свойств, отсутствующих в оригинальном классе. Получаемые классы могут иметь в точности такой же интерфейс, как и первоначальные, что позволяет прозрачно заменять их и оборачивать любое количество раз.
Это позволяет добавлять необходимые свойства объектам, не переписывая его с нуля. Предложенная обобщенная концепция будет последовательно введена и проиллюстрирована простыми, но интересными примерами.
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...Yandex
Флеш-накопители используются в самых разных устройствах, от мобильных телефонов до компьютеров и серверов. Для каждой модели накопителя нужна прошивка с определённым набором параметров, которые могут отличаться в зависимости от ситуации. В докладе будет описан универсальный фреймфорк на С++, который предоставляет разработчикам симуляторов простой, прозрачный и быстрый доступ к любому параметру. Тестировщикам же он позволяет управлять конфигурациями при помощи стандартных инструментов редактирования и слияния.
Григорий Демченко, Асинхронность и неблокирующая синхронизацияSergey Platonov
Практика показывает, что использование подхода, основанного на колбеках для асинхронного программирования обычно не является удобным и подвержено различным ошибкам. Для упрощения написания и поддержки сложных асинхронных программ можно использовать несколько иной подход: использовать сопрограммы для переключения контекста на время ожидания события. Такой подход позволяет реализовать интересные неблокирующие примитивы, включая неблокирующее сетевое взаимодействие, неблокирующие мьютексы, а также удобное переключение между различными пулами потоков для разнесения выполнения задач, которые требуют различные ресурсы.
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведенияPlatonov Sergey
В этом докладе Дмитрий кратко рассказывает о таком звере, как LLVM, о котором много кто слышал, но немногие щупали.
Что такое компилятор на самом деле? Как происходит компиляция программы, как работают оптимизации и, наконец, откуда берется неопределенное поведение в детерменированных программах на C++?
Андрей Карпов
Вы узнаете, что такое статический анализ кода и историю его развития. Узнаете, как эффективно применять инструменты статического анализа в своей работе, увидите практические примеры использования этой методологии. Доклад ориентирован на программистов, использующих языки Си/Си++, но будет полезен всем
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...Alexey Paznikov
ЛЕКЦИЯ 7. Модель памяти С++. Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...Alexey Paznikov
ЛЕКЦИЯ 2. POSIX Threads. Жизненный цикл потоков. Планирование. Синхронизация
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...Alexey Paznikov
ЛЕКЦИЯ 5. Многопоточное программирование в языке С++. Работа с потоками. Защита данных. Синхронизация. Будущие результаты
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ЛЕКЦИЯ 4. Стандарт POSIX Threads. Реентерабельность функций. Обработка сигналов. Локальные данные потоков. Принудительное завершение потоков. Шаблоны программирования с использованием потоков
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMSergey Platonov
Зачастую, знакомство с алиасингом в C++ у многих программистов начинается и заканчивается одинаково: -fno-strict-aliasing. На вопросы новичка, более опытные коллеги отвечают в стиле: «не трогай! а то все сломаешь!». Новичок и не трогает. В докладе будет предпринята попытка заглянуть под капот и понять, что же там, внутри. Что такое алиасинг, где он может быть полезен и какие реальные преимущества дает. Тема будет рассмотрена и со стороны программиста и со стороны разработчика компилятора. А по сему, вопрос «зачем?» будет центральным в повествовании.
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Sergey Platonov
В последнее время в промышленной разработке ПО особую популярность обретают Domain-Specific Lanugages (DSL). Они драматически упрощают разработку и дают возможность “программировать” не только программистам, но и пользователям прикладных программ.
В своем докладе я расскажу об опыте использования DSL применительно к С++, причем упор будет сделан на производительность кода DSL, и его мгновенную “встраиваемость” в запущенную программу путем компиляции DSL-кода в нативный код с помощью инструментария LLVM.
Модель памяти C++ - Андрей Янковский, ЯндексYandex
В докладе Андрей расскажет о моделях памяти различных процессоров, о тонкостях реализации неблокирующих алгоритмов и о том, какое отношение всё это имеет к С++.
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловSergey Platonov
В докладе перед нами откроется великолепный мир велосипедов и устаревших технологий, которые люди продолжают переносить в новые проекты и повсеместно использовать. Мы поговорим о:
Copy-On-Write
разработке без оглядки на готовые решения и к чему это приводит
force inline
оптимизациях, которые отлично себя показывают на бенчмарках и плохо себя ведут в реальной жизни
бездумно отключаемых оптимизациях компилятора
тонкостях стандартной библиотеки для повседневного использования
супер качественном велосипедостроении
Использование юнит-тестов для повышения качества разработкиvictor-yastrebov
В докладе рассмотрены подходы к созданию надежных юнит-тестов, которые просты в поддержке и модернизации, а также принципы создания кода пригодного для покрытия автотестами. Приведены два способа внедрения зависимости: с использованием конструктора тестируемого объекта, а также с использованием подхода "выделить и переопределить". Каждый из способов разобран на примере, демонстрирующем особенности его реализации и применения. Приведен ряд практических советов, нацеленных на создание надежных юнит-тестов. Использование на практике приведенных подходов и принципов позволяет упростить процесс поддержки и модификации существующего кода, а также дает уверенность в надежности работы добавляемого нового функционала. В конечном итоге это приводит к повышению качества разрабатываемого продукта.
Догнать и перегнать boost::lexical_castRoman Orlov
Разбор нестандартной реализации преобразования целого числа в строку без использования циклов и рекурсивных вызовов времени исполнения - только рекурсия на этапе компиляции
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Yandex
Основываясь на опыте разработки Крипты, Дмитрий рассмотрит средства реализации статического и динамического полиморфизма в C++, а также некоторые их паттерны и антипаттерны.
Доклад вводит в рассмотрение универсальный адаптер, позволяющий обернуть любой класс с целью добавления новых свойств, отсутствующих в оригинальном классе. Получаемые классы могут иметь в точности такой же интерфейс, как и первоначальные, что позволяет прозрачно заменять их и оборачивать любое количество раз.
Это позволяет добавлять необходимые свойства объектам, не переписывая его с нуля. Предложенная обобщенная концепция будет последовательно введена и проиллюстрирована простыми, но интересными примерами.
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...Yandex
Флеш-накопители используются в самых разных устройствах, от мобильных телефонов до компьютеров и серверов. Для каждой модели накопителя нужна прошивка с определённым набором параметров, которые могут отличаться в зависимости от ситуации. В докладе будет описан универсальный фреймфорк на С++, который предоставляет разработчикам симуляторов простой, прозрачный и быстрый доступ к любому параметру. Тестировщикам же он позволяет управлять конфигурациями при помощи стандартных инструментов редактирования и слияния.
Григорий Демченко, Асинхронность и неблокирующая синхронизацияSergey Platonov
Практика показывает, что использование подхода, основанного на колбеках для асинхронного программирования обычно не является удобным и подвержено различным ошибкам. Для упрощения написания и поддержки сложных асинхронных программ можно использовать несколько иной подход: использовать сопрограммы для переключения контекста на время ожидания события. Такой подход позволяет реализовать интересные неблокирующие примитивы, включая неблокирующее сетевое взаимодействие, неблокирующие мьютексы, а также удобное переключение между различными пулами потоков для разнесения выполнения задач, которые требуют различные ресурсы.
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведенияPlatonov Sergey
В этом докладе Дмитрий кратко рассказывает о таком звере, как LLVM, о котором много кто слышал, но немногие щупали.
Что такое компилятор на самом деле? Как происходит компиляция программы, как работают оптимизации и, наконец, откуда берется неопределенное поведение в детерменированных программах на C++?
Андрей Карпов
Вы узнаете, что такое статический анализ кода и историю его развития. Узнаете, как эффективно применять инструменты статического анализа в своей работе, увидите практические примеры использования этой методологии. Доклад ориентирован на программистов, использующих языки Си/Си++, но будет полезен всем
Юнит-тестирование и Google Mock. Влад Лосев, Googleyaevents
Владимир Лосев, Google
Закончил математико-механический факультет Санкт-Петербургского государственного университета в 1995 году. Работал в компаниях Motоrola, Fair Isaac и Yahoo. С 2008 года работает в Google, в группе, занимающейся вопросами повышения производительности инженеров.
Тема доклада
Юнит-тестирование и Google Mock.
Тезисы
В модульных (юнит) тестах каждый элемент программы тестируется по отдельности, в изоляции от других. Такие тесты исполняются очень быстро, поэтому их можно запускать когда угодно, что позволяет отлавливать дефекты на самых ранних стадиях разработки. Однако для тестирования объекта в изоляции от других необходимо имитировать поведение связанных с ним объектов, что на C++ довольно утомительное занятие. Разработанная в Googlе библиотека для создания и использования mock-объектов — Google Mock — позволяет существенно упростить этот процесс и ускорить написание тестов. В докладе пойдет речь о принципах и возможностях библиотеки, примерах её использования и её внутреннем устройстве.
Как показывает практика, повсеместное применение классического, основанного на callback’ах подхода к асинхронному программированию обычно оказывается неудобным. Для упрощения написания и поддержки сложного асинхронного кода можно использовать иной подход — с использованием сопрограмм. Он значительно сокращает объём и сложность кода, превращая код из асинхронного в синхронный.
Опыт разработки статического анализатора кодаAndrey Karpov
Один из основателей проекта PVS-Studio расскажет об опыте разработки статического анализатора кода C++. У инструментов статического анализа кода существует "проблема айсберга". От пользователей скрыты сложные механизмы анализа кода, и иногда им кажется, что статические анализаторы – это просто какие-то утилиты, ищущие опечатки с помощью регулярных выражений. Автор доклада постарается в общих чертах описать, как всё обстоит на самом деле. Он покажет на примерах, почему нормальный анализ с помощью регулярных выражений нереализуем, что такое Data Flow анализ, а также расскажет о других технологиях, применяемых при анализе кода. Вкратце будет затронут вопрос использования нейронных сетей, обсуждение которых сейчас является очень модной темой, и рассказано, почему с точки зрения анализа кода отношение к этому направлению является очень скептическим.
Статический анализ кода: Что? Как? Зачем?Andrey Karpov
Методология статического анализа год за годом зарекомендовывает себя в поисках дефектов в исходном коде программ.
Максим расскажет про:
- методологию статического анализа и какие плюсы и минусы у нее есть;
- технологии этой методологии, которые позволяют выявлять разнообразнейшие дефекты в коде;
- интересные примеры ошибок в реальных проектах, которые были найдены при помощи статического анализа;
- интеграцию инструментов статического анализа в проекты любой сложности, и почему так важно регулярное использование подобных инструментов.
Филипп Торчинский «Анализ производительности и отладка приложений с помощью D...Yandex
Научно-технический семинар «DTrace: Проверочная работа для вашего кода» в петербургском офисе Яндекса, 29 ноября 2012 г.
Филипп Торчинский, эксперт по облачным технологиям, Семоникс.
Любите ли вы велосипеды? Все разработчики любят свои ненаколеночныерешения велосипеды! И мы не исключение. В нашем докладе мы покажем как собирать, сколачивать, вылепливать собственный велосипед так, чтобы на нем потом могла ездить без слёз вся команда, компания, или может весь мир.
Что в докладе будет:
- много Spring Boot-а;
- live coding;
- создание собственного Spring Boot Starter-а;
- Apache Thrift в качестве подопытного кролика.
Чего не будет:
- бенчмарков и сравнений Thrift vs REST vs gRPC vs XXX.
Статический анализ: ищем ошибки... и уязвимости?Andrey Karpov
Новости об очередной найденной уязвимости регулярно всплывают то тут, то там. Сопутствующие потери $, как правило, колоссальны. Поэтому вместо исправления уязвимостей, следует не допускать их появления. Один из способов борьбы с ошибками в коде – использование статического анализа. Но насколько он подходит для поиска уязвимостей? И так ли велика разница между простыми ошибками и уязвимостями с точки зрения кода? Эти вопросы мы и обсудим в ходе доклада, а заодно поговорим о том, каким образом использовать статический анализ так, чтобы извлечь из него максимум пользы.
Статический и динамический полиморфизм в C++, Дмитрий ЛевановYandex
На примере некоторых архитектурных решений Крипты Дмитрий расскажет о способах реализации полиморфного поведения в программах на C++, о преимуществах и недостатках этих способов, а также о новых возможностях C++11.
Similar to ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель. Потокобезопасный стек (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
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...Alexey Paznikov
ЛЕКЦИЯ 2. Коллективные операции в стандарте 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. Живучие ВС. Потенциальная и структурная живучесть ВС. Показатели потенциальной живучести ВС
Пазников Алексей Александрович
к.т.н., ст. преп. Кафедры вычислительных системСибирский государственный университеттелекоммуникаций и информатики
ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель. Потокобезопасный стек
1. Лекция 7. Многопоточное
программирование без блокировок.
Модель потребитель-производитель.
Потокобезопасный стек
Пазников Алексей Александрович
Кафедра вычислительных систем СибГУТИ
Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/
Вопросы: https://piazza.com/sibsutis.ru/fall2014/pct14/home
Параллельные вычислительные технологии
Осень 2014 (Parallel Computing Technologies, PCT 14)
2. Программирование без
блокировок
2
Если вы думаете, что программирование без блокировок
это просто, значит или вы - один из тех 50, которые умеют
это делать, или же используете атомарные инструкции
недостаточно аккуратно.
Герб Саттер
3. Цели многопоточного программирования без блокировок
3
Однопоточная программа
Многопоточная программа
с блокировками
Многопоточная программа
без блокировок
4. Цели многопоточного программирования без блокировок
▪ Повышение масштабируемости путём
сокращения блокировок и ожиданий в алгоритмах
и структурах данных.
▪ Решение проблем, связанных с блокировками:
▫ Гонки: нужно не забыть заблокировать,
причём именно нужный участок кода
▫ Дедлоки: необходимо запирать в нужном
порядке различные потоки
▫ Сложность выбора критической секции
(простота или масштабируемость)
▫ Голоданеие, инверсия приоритетов и др.
4
5. Виды алгоритмов, свободных от блокировок
▪ Свободные от ожиданий (wait-free). “Никто никогда не ждёт”.
Каждая операция завершается за N шагов без каких-либо условий.
Гарантии:
▫ максимум пропускной способности системы
▫ отсутствие голодания
▪ Свободные от блокировок (lock-free). “Всегда какой-то из потоков
работает”. Каждый шаг приближает итоговое решение. Гарантии:
▫ максимум пропускной способности системы
▫ отсутствие голодания (один поток может постоянно ожидать)
▪ Свободные от остановок (obstruction-free). “Поток работает, если
нет конфликтов”. За ограниченное число шагов один поток, при
условии, что другие потоки остановлены, достигает результата.
▫ Все потоки не блокируются из-за проблем (задержек, ошибок) с
другими потоками.
▫ Не гарантируется прогресс, если одновременно работают два
или больше потоков. 5
6. Реализация спинлока на основе атомарного флага
class spinlock_mutex
{
std::atomic_flag flag;
public:
spinlock_mutex():
flag{ATOMIC_FLAG_INIT} { }
void lock() {
while (flag.test_and_set(
std::memory_order_acquire));
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
n.b. Можно использовать с lock_guard и unique_guard! 6
20. Производитель
20
curr = 0; // указатель на текущий слот
while (ThereAreMoreTasks()) {
task = AllocateAndBuildNewTask();
while (slot[curr] != null) // если null, то проверить
curr = (curr + 1) % numOfConsumers; // следующий слот
slot[curr] = task;
sem[curr].signal();
}
21. Производитель
21
curr = 0; // указатель на текущий слот
while (ThereAreMoreTasks()) {
task = AllocateAndBuildNewTask();
while (slot[curr] != null) // если null, то проверить
curr = (curr + 1) % numOfConsumers; // следующий слот
slot[curr] = task;
sem[curr].signal();
}
// Фаза 2: выставить флаги “done” во всех слотах
numNotified = 0;
while (numNotified < numOfConsumers) {
while (slot[curr] != null) // если null, то проверить
curr = (curr + 1) % numOfConsumers; // следующий
slot[curr] = done; // освободить слот
sem[curr].signal();
++numNotified;
}
22. Потребитель
22
task = null;
while (task != done)
// Дождаться, когда слот будет полным
while ((task = slot[mySlot]) == null)
sem[mySlot].wait();
if (task != done) {
slot[mySlot] = null; // помечаем слот пустым
DoWork(task); // выполняем задачу
} // вне критической секции
}
23. Потребитель
23
task = null;
while (task != done)
// Дождаться, когда слот будет полным
while ((task = slot[mySlot]) == null)
sem[mySlot].wait();
if (task != done) {
slot[mySlot] = null; // помечаем слот пустым
DoWork(task); // выполняем задачу
} // вне критической секции
}
Как применить модель памяти С++?
24. Производитель, модель памяти С++
24
curr = 0;
while (ThereAreMoreTasks()) {
task = AllocateAndBuildNewTask();
while (slot[curr] != null) // acquire null
curr = (curr + 1) % numOfConsumers;
slot[curr] = task; // release non-null
sem[curr].signal();
}
// Фаза 2: выставить флаги “done” во всех слотах
numNotified = 0;
while (numNotified < numOfConsumers) {
while (slot[curr] != null) // acquire null
curr = (curr + 1) % numOfConsumers;
slot[curr] = done; // release done
sem[curr].signal();
++numNotified;
}
25. Птребитель, модель памяти С++
25
task = null;
while (task != done)
// Дождаться, когда слот будет полным
while ((task = slot[mySlot]) // acquire non-null
== null)
sem[mySlot].wait();
if (task != done) {
slot[mySlot] = null; // release null
DoWork(task);
}
}
26. Производитель-потребитель, класс алгоритма
26
curr = 0;
while (ThereAreMoreTasks()) {
task = AllocateAndBuildNewTask();
while (slot[curr] != null) // acquire null
curr = (curr + 1) % numOfConsumers;
slot[curr] = task; // release non-null
sem[curr].signal();
}
// Фаза 2: выставить флаги “done” во всех слотах
numNotified = 0;
while (numNotified < numOfConsumers) {
while (slot[curr] != null) // acquire null
curr = (curr + 1) % numOfConsumers;
slot[curr] = done; // release done
sem[curr].signal();
++numNotified;
}
Алгоритм - свободный от ожиданий, свободный от
блокировок или свободный от остановок?
27. Производитель-потребитель, класс алгоритма
27
curr = 0;
while (ThereAreMoreTasks()) {
task = AllocateAndBuildNewTask();
while (slot[curr] != null) // acquire null
curr = (curr + 1) % numOfConsumers;
slot[curr] = task; // release non-null
sem[curr].signal();
}
// Фаза 2: выставить флаги “done” во всех слотах
numNotified = 0;
while (numNotified < numOfConsumers) {
while (slot[curr] != null) // acquire null
curr = (curr + 1) % numOfConsumers;
slot[curr] = done; // release done
sem[curr].signal();
++numNotified;
}
Алгоритм - свободный от ожиданий, свободный от
блокировок или свободный от остановок?
Этап 2:
Свободная от остановок
Этап 1:
Свободный от ожиданий
28. Производитель-потребитель без блокировок
28
task = null;
while (task != done)
// Дождаться, когда слот будет полным
while ((task = slot[mySlot]) == null)
sem[mySlot].wait();
if (task != done) {
slot[mySlot] = null;
DoWork(task);
}
}
можно ли поменять две строки?
нужно ли это сделать?
30. Стек, свободный от блокировок
30
T T T T
head
1. Конструктор
2. Деструктор
3. Поиск узла (find)
4. Добавление узла (push)
5. Удаление узла (pop)
31. Стек, свободный от блокировок
31
template <typename T>
class lfstack {
public:
lfstack();
~lfstack();
T* find(T data) const; // найти элемент, равный data
void push(T data); // добавить элемент в голову
private:
struct node { // атомарные операции
T data; // не требуются
node* next;
};
std::atomic<node*> head{nullptr}; // атомарный указатель
}; // на голову списка
32. Конструктор и деструктор
32
template <typename T>
lfstack<T>::lfstack() {}
Объект создаётся в одном потоке, поэтому не нужно обеспечивать
параллельный доступ. Нельзя использовать стек до тех пор, пока он
не будет создан, т.е. до выполнения конструктора, и после того, как
он будет уничтожен, т.е. после выполнения деструктора.
template <typename T>
lfstack<T>::~lfstack() {
auto first = head.load();
while (first) {
auto unlinked = first;
first = first->next;
delete unlinked;
}
}
33. Функция push
33
1. Создать новый узел.
2. Записать в его указатель next текущее значение head.
3. Записать в head указатель на новый узел.
void push(T const& data) {
auto new_node = new node{data}; // (1)
node_node->next = head.load(); // (2)
head = new_node; // (3)
}
struct node {
T data;
node* next;
node(T const& _data): data{_data} {}
};
36. Функция push
36
void push(T const& data) {
auto new_node = new node{data}; // (1)
node_node->next = head.load(); // (2)
head = new_node; // (3)
while (!head.compare_exchange_weak(new_node->next,
new_node)); // (3)
}
1. Создать новый узел.
2. Записать в его указатель next текущее значение head.
3. Записать в head указатель на новый узел, при этом с
помощью операции сравнить-и-обменять гарантировать
то, что head не был модифицирован с момента
чтения на шаге 2.
37. Функция pop (ошибочная)
37
void pop(T& result) {
node* old_head = head.load();
head = head->next;
result = old_head->data;
}
40. Функция pop (ошибочная)
40
void pop(T& result) {
node* old_head = head.load();
while (!head.compare_exchange_weak(old_head,
old_head->next);
result = old_head->data;
}
41. Функция pop (ошибочная)
41
void pop(T& result) {
node* old_head = head.load();
while (!head.compare_exchange_weak(old_head,
old_head->next);
result = old_head->data;
}
42. Функция pop (ошибочная)
42
void pop(T& result) {
node* old_head = head.load();
while (!head.compare_exchange_weak(old_head,
old_head->next);
result = old_head->data;
}
std::shared_ptr<T> pop(T& result) {
node* old_head = head.load();
while (old_head &&
!head.compare_exchange_weak(old_head,
old_head->next));
return old_head ?
old_head->data : std::shared_ptr<T>();
}
46. Проблема АВА
46
4
old_head
321
head->next
43
Поток А был вытеснен и другие
потоки удалили два узла из стека
Поток А выполняет удаление узла
из вершины стека
435
old_head old_head->next
old_head old_head->next
Некий поток добавил новый узел и
аллокатор выделил под него ту же память
47. Проблема АВА
47
4
old_head
321
head->next
43
Поток А выполняет удаление узла
из вершины стека
43
Некий поток добавил новый узел и
аллокатор выделил под него ту же память
5
43
Поток А: head.compare_exchange(
old_head, old_head->next))
5
old_head old_head->next
old_head old_head->next
old_head old_head->next
Поток А был вытеснен и другие
потоки удалили два узла из стека
48. Проблема АВА
48
4
old_head
321
head->next
43
Поток А выполняет удаление узла
из вершины стека
435
43
Некий поток добавил новый узел и
аллокатор выделил под него ту же память
Поток А: head.compare_exchange(
old_head, old_head->next))
5
old_head old_head->next
old_head old_head->next
old_head old_head->next
Поток А был вытеснен и другие
потоки удалили два узла из стека
49. Решения проблемы АВА
49
1. “Ленивый” сборщик мусора
2. Указатели опасности
3. Счётчик ссылок на элемент
4. Сделать узлы уникальными
5. Вообще не удалять узлы
6. Добавление дополнительных узлов
7. и т.д.
50. Функция pop (наивная)
50
// количество потоков, выполняющих pop
std::atomic<unsigned> threads_in_pop;
std::shared_ptr<T> pop() {
threads_in_pop++;
node* old_head = head_load();
while (old_head &&
!head.compare_exchange_weak(old_head,
old_head->next));
std::shared_ptr<T> res;
if (old_head)
res.swap(old_head->data); // не копировать,
// а обменять данные
try_reclaim(old_head); // попробовать освободить
// удалённые узлы
return res;
}
51. Функция pop (наивная)
51
template<typename T>
class lfstack {
private:
std::atomic<node*> delete_list;
static void delete_nodes(node* nodes);
while (nodes) {
node* next = nodes->next;
delete nodes;
nodes = next;
}
}
52. Функция try_reclaim освобождения удалённых узлов
52
void try_reclaim(node* old_head) {
if (threads_in_pop == 1) { // я единственный в pop?
node* nodes_to_delete = // захватить список
delete_list.exchange{nullptr}; // на удаление
if (!--thread_in_pop) // точно единственный?
delete_nodes(nodes_to_delete)); // удалить всё!
else if (nodes_to_delete) // если в захваченном списке
// что-то было
// вернуть это в общий список узлов на удаление
chain_pending_nodes(nodes_to_delete);
delete old_head; // удаляем хотя бы только что
// исключённый узел
} else {
// удалим узел как-нибудь потом
chain_pending_node(old_head);
--threads_in_pop;
}
}
53. Функция try_reclaim освобождения удалённых узлов
53
// добавляем захваченный список в общий список узлов,
// подлежащих удалению
void chain_pending_nodes(node* nodes) {
node* last = nodes;
while (node* const next = last->next)
last = next;
chain_pending_nodes(nodes, last);
}
// добавить список узлов в список узлов на удаление
void chain_pending_nodes(node* first, node* last) {
last->next = delete_list;
while (!delete_list.compare_exchange_weak(last->next,
first));
}
// добавить узел в список узлов на удаление
void chain_pending_node(node* n) {
chain_pending_nodes(n, n);
}
54. Функция try_reclaim освобождения удалённых узлов
54
4
head
321
delete_list 0threads_in_pop
4
head
321
delete_list
threads_in_pop 1
5
5
A A удаляет узел 1 и
вытесняется в pop() после
1-го чтения threads_in_pop
55. Функция try_reclaim освобождения удалённых узлов
55
4
head
32
delete_list 2threads_in_pop
43
delete_list
threads_in_pop 2
5
2
С удаляет узел и
продолжает работать до
момента выхода из pop()
old_head
B
B вызывает pop() и
вытесняется после 1-го
чтения head
A
head
old_head
B
4
C
5
A
56. Функция try_reclaim освобождения удалённых узлов
56threads_in_pop 2
43
delete_list
threads_in_pop 2
2
A возобновляет выполнение
и захватывает список на
удаление. После этого он
должен 2-й раз проверить,
один ли он в pop()
head
old_head
B
2
5
A
43
head
old_head
B
2
delete_list A
2 5
delete_list
B возобновляет выполнение,
выполняет CAS и переходит
к 3 узлу
58. Указатели опасности (hazard pointers)
58
4
old_head
321
head->next
43
Поток А выполняет удаление узла
из вершины стека и помечает узел 1
как узел, который он использует.
old_head old_head->next
Поток А был вытеснен и другие потоки
удалили два узла из стека, но не
освобождают память из-под первого узла.
2
head
head
1
A “понимает”, что головной узел
head изменился и нужно
выполнить compare_exchange
43
old_head old_head->next
21
59. Функция pop на основе указателей опасности
59
std::shared_ptr<T> pop() {
std::atomic<void*>& hp =
get_hazard_pointer_for_current_thread();
// установить указатель опасности перед чтением указателя,
// который мы собираемся разыменовывать
node* old_head = head.load();
node* temp;
do {
temp = old_head;
hp.store(old_head);
old_head = head.load();
} while (old_head != temp);
// ...
}
60. Функция pop на основе указателей опасности
60
std::shared_ptr<T> pop() {
std::atomic<void*>& hp =
get_hazard_pointer_for_current_thread();
node* old_head = head.load();
do {
node* temp;
do {
temp = old_head;
hp.store(old_head); // устанавливаем УО
old_head = head.load();
} while (old_head != temp);
} while (old_head && // получаем узел
!head.compare_exchange_strong(old_head,
old_head->next));
hp.store(nullptr);
64. Указатели опасности (hazard pointers)
64
4
old_head
321
temp = old_head
head
temp
hp.store(old_head)
old_head = head.load()
“old old_head”
“new old_head”
hp
65. Указатели опасности (hazard pointers)
65
4
old_head
321
temp = old_head
head
temp
hp.store(old_head)
old_head = head.load()
“old old_head”
“new old_head”
hp
== ?
Таким образом, внутренний цикл гарарантирует то, что указатель
опасности будет указывать на тот головной элемент head, с котором
мы будем работать (сдвигать указатель на следующий элемент)
Проверка позволяет определить, не изменился ли головной элемент с
тех пор, когда мы запомнили его в указателе опасности.
66. Указатели опасности (hazard pointers)
66
4
old_head
321
temp = old_head
head
temp
hp.store(old_head)
old_head = head.load()
“old old_head”
“new old_head”
hp
== ?
Во внешнем цикле сдвигаем указатель с head на следующий
элемент с уверенностью, что никто не подменит элемент head.
67. Указатели опасности (hazard pointers)
67
После того, как поток А успешно
выполнил compare_exchange,
указатель опасности можно обнулять
hp.store(nullptr), т.к. никто пока
не сможет удалить old_head, кроме
А, поскольку head изменён потоком А
43
old_head old_head->next
21
Вариант 1
43
old_head->next
1
Вариант 2
old_head
2
68. Функция pop на основе указателей опасности
68
std::shared_ptr<T> res;
if (old_head) {
res.swap(old_head->data); // извлекаем данные
if (outstanding_hazard_pointers_for(old_head))
// если опасно, удаляем потом
reclaim_later(old_head);
else
// если не опасно, удаляем сейчас
delete old_head;
// пробуем удалить узлы, какие можно удалить
delete_nodes_with_no_hazards();
}
return res;
}
70. Реализация указателей опасности
70
4321
head
1 5 6 7 m432
Указатели опасности, m = max_hazard_pointers
пустой?
нет
if (hazard_pointers[i].id.
compare_exchange_strong(
old_id,
std::this_thread::get_id()))
thread_local
hp
71. Реализация указателей опасности
71
4321
head
1 5 6 7 m432
Указатели опасности, m = max_hazard_pointers
пустой?
да
if (hazard_pointers[i].id.
compare_exchange_strong(
old_id,
std::this_thread::get_id()))
thread_local
hp
72. Реализация указателей опасности
72
4321
head
1 5 6 7 m432
Указатели опасности, m = max_hazard_pointers
пустой?
да
if (hazard_pointers[i].id.
compare_exchange_strong(
old_id,
std::this_thread::get_id()))
thread_local
hp
74. Реализация указателей опасности
74
hp_owner(): hp{nullptr} {
for (auto i = 0; i < max_hazard_pointers; i++) {
std::thread::id old_id; // пустой незанятый УО
// если i-й УО не занят, завладеть им, записав в него
// свой идентификатор потока
if (hazard_pointers[i].id.compare_exchange_strong(
old_id, std::this_thread::get_id())) {
hp = &hazard_pointers[i]; // я владею i-м УО
break;
}
}
// таблица УО закончилась, указателей нам не досталось
if (!hp)
throw std::runtime_error("No hazard ptrs available");
}
75. Реализация указателей опасности
75
hp_owner(): hp{nullptr} {
for (auto i = 0; i < max_hazard_pointers; i++) {
std::thread::id old_id;
if (hazard_pointers[i].id.compare_exchange_strong(
old_id, std::this_thread::get_id())) {
hp = &hazard_pointers[i];
break;
}
}
if (!hp)
throw std::runtime_error("No hazard ptrs available");
}
std::atomic<void*>& get_pointer() {
return hp->pointer;
}
~hp_owner() {
hp->pointer.store(nullptr);
hp->id.store(std::thread::id());
}
76. Реализация указателей опасности
76
// вернуть указатель опасности для текущего потока
std::atomic<void*>&
get_hazard_pointer_for_current_thread() {
thread_local static hp_owner hazard;
return hazard.get_pointer();
}
77. Реализация указателей опасности
77
// вернуть указатель опасности для текущего потока
std::atomic<void*>&
get_hazard_pointer_for_current_thread() {
thread_local static hp_owner hazard;
return hazard.get_pointer();
}
// проверить, не ссылается ли на указатель какой-то из УО
bool outstanding_hazard_pointers_for(void* p) {
for (auto i = 0; i < max_hazard_pointers; i++) {
if (hazard_pointers[i].pointer.load() == p) {
return true;
}
}
return false;
}
78. Реализация функции освобождения памяти
78
template <typename T>
void do_delete(void* p) {
delete static_cast<T*>(p);
}
struct data_to_reclaim { // обёртка над данными для
void* data; // помещения в список удаления
std::function<void(void*)> deleter;
data_to_reclaim* next;
template<typename T>
data_to_reclaim(T* p):
data{p}, deleter{&do_delete<T>}, next{0} { }
~data_to_reclaim() {
deleter(data);
}
};
std::atomic<data_to_reclaim*> nodes_to_reclaim;
79. Реализация функции освобождения памяти
79
// добавить элемент в список на удаление
void add_to_reclaim_list(data_to_reclaim* node) {
node->next = nodes_to_reclaim.load();
while (!nodes_to_reclaim.compare_exchange_weak(
node->next, node));
}
// удалить элемент позже
template<typename T>
void reclaim_later(T* data) {
add_to_recalim_list(new data_to_reclaim(data));
}
80. Реализация функции освобождения памяти
80
void delete_nodes_with_no_hazards() {
// захватить текущий список
data_to_reclaim* current =
nodes_to_reclaim.exchange(nullptr);
while (current) {
data_to_reclaim* const next = current->next;
if (!outstanding_hazard_pointers_for(current->data))
// если не опасно, удалить сейчас
delete current;
else
// если опасно удалить потом
add_to_reclaim_list(current);
current = next;
}
}
89. Недостатки указетелей опасности
89
1. Просмотр массива указателей опаности требует в худшем случае
max_hazard_pointers атомарных переменных.
2. Атомарные операции могут работать медленнее эквивалентных
обычных операций
3. При освобождении узла также требуется просмотреть список
указателей опаности, т.е. max_hazard_pointers в худшем случае.
Функция pop дорогостоящая. Решения?
90. Недостатки указетелей опасности
90
1. Просмотр массива указателей опаности требует в худшем случае
max_hazard_pointers атомарных переменных.
2. Атомарные операции могут работать медленнее эквивалентных
обычных операций
3. При освобождении узла также требуется просмотреть список
указателей опаности, т.е. max_hazard_pointers в худшем случае.
Функция pop дорогостоящая. Решения?
1. Вместо просмотра max_hazard_pointers в каждом pop(),
проверяем 2 * max_hazard_pointers через каждые
max_hazard_pointers вызовов pop() и освобождаем не менее
max_hazard_pointers. В среднем проверяем два узла при
каждом вызове pop() и один освобождаем.
2. Каждый поток хранит собственный список освобождения в
локальных данных потока. Это потребует выделения памяти под
max_hazard_pointers2
узлов.
92. Реализация на основе атомарного умного указателя
92
▪ Удалять узлы можно только при отсутствии
обращения к ним из других потоков
▪ Если на узел нет ссылки, то его можно
удаляь
93. Реализация на основе атомарного умного указателя
93
▪ Удалять узлы можно только при отсутствии
обращения к ним из других потоков
▪ Если на узел нет ссылки, то его можно
удаляь
▪ Умный указатель shared_ptr как раз
решает эту задачу!
94. Реализация на основе атомарного умного указателя
94
▪ Удалять узлы можно только при отсутствии
обращения к ним из других потоков
▪ Если на узел нет ссылки, то его можно
удаляь
▪ Умный указатель shared_ptr как раз
решает эту задачу!
...
▪ Но, к сожалению, атомарные операции
shared_ptr в большинстве реализаций не
свободны от блокировок.
98. Двойной счётчик ссылок
98
counted_node_ptr
node
internal_count
data
next
external_count
▪ При начале каждого чтения внешний счётчик увеличивается.
▪ При завершении чтения внутренний счётчик уменьшается.
▪ При удалении узла внутренний счетчик увеличивается на
величину внешнего минус 1, а внешний отбрасывается.
▪ Если внутренний счётчик равен 0, узел можно удалять.
112. 0 0 0
Двойной счётчик ссылок
112
1 1 1
head
1 2 3
Сценарий 2:
Потоки А и В одновременно удаляют узел.
Потоку А удаётся выполнить удаление узла вперёд B.
Поток В успевает выйти из pop до того,
как А попробует освободить узел.
118. 0 0 0
Двойной счётчик ссылок
118
1 1 1
head
1 2 3
Сценарий 3:
Потоки А и В одновременно удаляют узел.
Потоку А удаётся выполнить удаление узла вперёд B.
Поток В не успевает выйти из pop, когда А пытается
освободить узел, и поэтому А узел не освобождает.
Зато поток В, последним покидая узел,
освобождает память из-под узла, удалённого А.
122. Двойной счётчик ссылок - проблема
122
template<typename T>
class lfstack {
private:
struct counted_node_ptr {
int external_count;
node* ptr;
};
struct node {
std::shared_ptr<T> data;
std::atomic<int> internal_count;
counted_node_ptr next;
node(T const& _data):
data(std::make_shared<T>(_data)), internal_count(0) {}
};
std::atomic<counted_node_ptr> head;
Структура может не поддерживать выполнение
атомарных операций без блокировок!
124. Применение модели памяти С++
124
void push(T const& data) {
counted_node_ptr new_node;
new_node.ptr = new node(data);
new_node.external_count = 1;
new_node.ptr->next =
head.load(std::memory_order_relaxed);
while (!head.compare_exchange_weak(new_node.ptr->next,
new_node));
void increase_head_count(counted_node_ptr& old_counter) {
counted_node_ptr new_counter;
do { new_counter = old_counter;
++new_counter.external_count;
} while (!head.compare_exchange_strong(old_counter,
new_counter));
old_counter.external_count = new_counter.external_count;
}
125. Применение модели памяти С++
125
void push(T const& data) {
counted_node_ptr new_node;
new_node.ptr = new node(data);
new_node.external_count = 1;
new_node.ptr->next =
head.load(std::memory_order_relaxed);
while (!head.compare_exchange_weak(new_node.ptr->next,
new_node));
void increase_head_count(counted_node_ptr& old_counter) {
counted_node_ptr new_counter;
do { new_counter = old_counter;
++new_counter.external_count;
} while (!head.compare_exchange_strong(old_counter,
new_counter));
old_counter.external_count = new_counter.external_count;
}
Подготовка
данных
Установка head
(“флага”)
Проверка head
(“флага”)
Работа с добавленным элементом
126. Применение модели памяти С++
126
void push(T const& data) {
counted_node_ptr new_node;
new_node.ptr = new node(data);
new_node.external_count = 1;
new_node.ptr->next =
head.load(std::memory_order_relaxed);
while (!head.compare_exchange_weak(new_node.ptr->next,
new_node, std::memory_order_release,
std::memory_order_relaxed));
void increase_head_count(counted_node_ptr& old_counter) {
counted_node_ptr new_counter;
do { new_counter = old_counter;
++new_counter.external_count;
} while (!head.compare_exchange_strong(old_counter,
std::memory_order_acquire,
std::memory_order_relaxed, new_counter));
old_counter.external_count = new_counter.external_count;
}
Подготовка
данных
Установка head
(“флага”)
Работа с добавленным элементом
Проверка head
(“флага”)
127. Применение модели памяти С++
127
std::shared_ptr<T> pop() {
counted_node_ptr old_head = head.load();
for (;;) {
increase_head_count(old_head);
node* const ptr = old_head.ptr;
if (!ptr) return std::shared_ptr<T>();
if (head.compare_exchange_strong(old_head, ptr->next)) {
std::shared_ptr<T> res;
res.swap(ptr->data);
int const count_increase = old_head.external_count - 2;
if (ptr->internal_count.fetch_add(count_increase) ==
-count_increase)
delete ptr;
return res;
} else if (ptr->internal_count.fetch_sub(1) == 1)
delete ptr;
}
}
Чтение указателя
128. Применение модели памяти С++
128
std::shared_ptr<T> pop() {
counted_node_ptr old_head = head.load();
for (;;) {
increase_head_count(old_head);
node* const ptr = old_head.ptr;
if (!ptr) return std::shared_ptr<T>();
if (head.compare_exchange_strong(old_head, ptr->next,
std::memory_order_relaxed)) {
std::shared_ptr<T> res;
res.swap(ptr->data);
int const count_increase = old_head.external_count - 2;
if (ptr->internal_count.fetch_add(count_increase) ==
-count_increase)
delete ptr;
return res;
} else if (ptr->internal_count.fetch_sub(1) == 1)
delete ptr;
}
}
Чтение указателя
acquire не нужен, т.к.
захват выполнен в
increase_head_count
129. Применение модели памяти С++
129
std::shared_ptr<T> pop() {
counted_node_ptr old_head = head.load();
for (;;) {
increase_head_count(old_head);
node* const ptr = old_head.ptr;
if (!ptr) return std::shared_ptr<T>();
if (head.compare_exchange_strong(old_head, ptr->next,
std::memory_order_relaxed)) {
std::shared_ptr<T> res;
res.swap(ptr->data);
int const count_increase = old_head.external_count - 2;
if (ptr->internal_count.fetch_add(count_increase) ==
-count_increase)
delete ptr;
return res;
} else if (ptr->internal_count.fetch_sub(1) == 1)
delete ptr;
}
}
Извлечение данных
Удаление должно выполняться
после извлечения данных
130. Применение модели памяти С++
130
std::shared_ptr<T> pop() {
counted_node_ptr old_head = head.load();
for (;;) {
increase_head_count(old_head);
node* const ptr = old_head.ptr;
if (!ptr) return std::shared_ptr<T>();
if (head.compare_exchange_strong(old_head, ptr->next,
std::memory_order_relaxed)) {
std::shared_ptr<T> res;
res.swap(ptr->data);
int const count_increase = old_head.external_count - 2;
if (ptr->internal_count.fetch_add(count_increase,
std::memory_order_release) == -count_increase)
delete ptr;
return res;
} else if (ptr->internal_count.fetch_sub(1) == 1)
delete ptr;
}
}
Удаление должно выполняться
после извлечения данных
Извлечение данных
131. Применение модели памяти С++
131
std::shared_ptr<T> pop() {
counted_node_ptr old_head = head.load();
for (;;) {
increase_head_count(old_head);
node* const ptr = old_head.ptr;
if (!ptr) return std::shared_ptr<T>();
if (head.compare_exchange_strong(old_head, ptr->next,
std::memory_order_relaxed)) {
std::shared_ptr<T> res;
res.swap(ptr->data);
int const count_increase = old_head.external_count - 2;
if (ptr->internal_count.fetch_add(count_increase,
std::memory_order_release) == -count_increase)
delete ptr;
return res;
} else if (ptr->internal_count.fetch_sub(1,
std::memory_order_acquire) == 1)
delete ptr;
}
Удаление должно выполняться
после извлечения данных
Извлечение данных
132. Применение модели памяти С++
132
std::shared_ptr<T> pop() {
counted_node_ptr old_head = head.load();
for (;;) {
increase_head_count(old_head);
node* const ptr = old_head.ptr;
if (!ptr) return std::shared_ptr<T>();
if (head.compare_exchange_strong(old_head, ptr->next,
std::memory_order_relaxed)) {
std::shared_ptr<T> res;
res.swap(ptr->data);
int const count_increase = old_head.external_count - 2;
if (ptr->internal_count.fetch_add(count_increase,
std::memory_order_release) == -count_increase)
delete ptr;
return res;
} else if (ptr->internal_count.fetch_sub(1,
std::memory_order_relaxed) == 1)
ptr->internal_count.load(std::memory_order_acquire);
delete ptr;
}
Достаточно вставить операцию захвата-
загрузки, чтобы удалить ptr после извлечения
данных
Извлечение данных
fetch_sub входит
в последовательность
освобождений, поэтому
“не мешает” acquire