РАЗРАБОТКА МЕХАНИЗМА ОБНАРУЖЕНИЯ КОНФЛИКТОВ ПАРАЛЛЕЛЬНОГО ДОСТУПА К ОБЩИМ РЕСУРСАМ В МНОГОПОТОЧНЫХ ПРИЛОЖЕНИЯХ
Конференция: CCCLIV Студенческая международная научно-практическая конференция «Молодежный научный форум»
Секция: Технические науки

CCCLIV Студенческая международная научно-практическая конференция «Молодежный научный форум»
РАЗРАБОТКА МЕХАНИЗМА ОБНАРУЖЕНИЯ КОНФЛИКТОВ ПАРАЛЛЕЛЬНОГО ДОСТУПА К ОБЩИМ РЕСУРСАМ В МНОГОПОТОЧНЫХ ПРИЛОЖЕНИЯХ
DEVELOPMENT OF A MECHANISM FOR DETECTING CONCURRENT ACCESS CONFLICTS TO SHARED RESOURCES IN MULTITHREADED APPLICATIONS
Bilyak Nikita Igorevich
Student, Moscow State University of Technology "STANKIN", Russia, Moscow
Многопоточные серверные приложения широко применяются для повышения пропускной способности систем, обрабатывающих одновременные запросы к общим структурам данных — кэшам, счётчикам, пулам соединений. Одновременный доступ нескольких потоков к общему ресурсу без должной синхронизации приводит к состояниям гонки, результат которых зависит от непредсказуемого порядка выполнения операций и трудно воспроизводится при отладке [1, с. 412]. Ошибки такого рода редко проявляются на этапе тестирования и часто обнаруживаются уже в промышленной эксплуатации, что делает задачу их раннего выявления особенно значимой для разработки надёжного программного обеспечения.
Классические средства синхронизации — мьютексы, семафоры, условные переменные — предотвращают состояния гонки только при корректном и последовательном их применении разработчиком [2, с. 34]. На практике же нарушения порядка захвата блокировок, забытые критические секции или обращение к разделяемым полям в обход синхронизации остаются частой причиной дефектов. Существующие инструменты диагностики — статические анализаторы кода и динамические санитайзеры — либо дают большое число ложных срабатываний при анализе крупных кодовых баз, либо существенно замедляют выполнение программы при динамической проверке, что ограничивает их применение в процессах непрерывной интеграции с жёсткими временными рамками.
Целью работы является разработка механизма, позволяющего выявлять конфликты параллельного доступа к общим ресурсам на этапе сборки приложения при приемлемых накладных расходах. Материалом исследования послужил исходный код серверных приложений на языке C++ и Java, использующих потоковые пулы для обработки запросов. Применялись методы статического анализа графа обращений к разделяемым полям, динамическая трассировка захвата блокировок и сравнение с существующими решениями — библиотекой Intel Threading Building Blocks и инструментом LLVM ThreadSanitizer [4].
Разработанный механизм состоит из двух взаимодополняющих компонентов. Первый компонент выполняет статическое построение карты доступа к разделяемым полям: для каждого класса или модуля определяются поля, к которым обращаются несколько потоков, и сопоставленные с ними точки синхронизации. Это позволяет заранее выявить поля, обращение к которым происходит без блокировки хотя бы в одном из путей выполнения кода.
Второй компонент осуществляет выборочную динамическую трассировку во время выполнения тестового набора: инструментируются только обращения к полям, отмеченным статическим анализом как потенциально небезопасные, что существенно сокращает накладные расходы по сравнению со сплошной трассировкой всех операций [3, с. 96]. При обнаружении пересекающихся по времени обращений к одному полю без общей блокировки фиксируется конфликт с указанием стека вызовов обоих потоков, что упрощает локализацию причины ошибки разработчиком.
Таблица 1.
Сравнение подходов к выявлению конфликтов параллельного доступа
|
Подход |
Полнота охвата |
Накладные расходы |
Ложные срабатывания |
|
Статический анализ |
Высокая |
Отсутствуют |
Высокие |
|
Сплошная трассировка |
Высокая |
Высокие |
Низкие |
|
Предложенный механизм |
Высокая |
Умеренные |
Низкие |
Экспериментальная проверка на наборе серверных приложений с известными состояниями гонки показала, что предварительная статическая фильтрация полей позволяет сократить объём инструментируемого кода в несколько раз по сравнению со сплошной трассировкой при сохранении сопоставимой полноты обнаружения конфликтов. Это делает механизм пригодным для регулярного запуска в процессе непрерывной интеграции, а не только при точечной диагностике уже выявленной проблемы.
Ограничением подхода является зависимость статического этапа от полноты информации о типах в анализируемом коде: динамическая диспетчеризация вызовов и обращения через рефлексию снижают точность построения карты доступа к полям. Дальнейшее развитие механизма связано с расширением анализа на распределённые сценарии, где общим ресурсом выступает не поле в памяти процесса, а запись во внешнем хранилище.
Предложенный двухкомпонентный механизм сочетает статическое построение карты обращений к разделяемым полям с выборочной динамической трассировкой, что позволяет снизить накладные расходы диагностики без потери полноты выявления конфликтов параллельного доступа и делает возможным его встраивание в регулярный цикл сборки и тестирования приложения.





