По возможности используйте инструменты для проверки вашего конкурентного кода
Опыт показывает, что конкурентный код исключительно сложно написать правильно и что проверка во время компиляции, проверка во время выполнения и тестирование менее эффективны при поиске ошибок параллелизма чем при поиске ошибок в последовательном коде. Тонкие ошибки параллелизма могут иметь драматически плохие эффекты, включая повреждение памяти, deadlock'и и уязвимости безопасности.
Пример
???
Примечание
Безопасность потоков — это сложная задача, часто побеждающая опытных программистов: инструменты — это важная стратегия для смягчения этих рисков. По всему миру существует много инструментов, как коммерческих, так и инструменты с открытым исходным кодом, как исследовательские, так и производственные инструменты. К сожалению, потребности и ограничения людей различаются так сильно, что мы не можем дать конкретные рекомендации, но мы можем упомянуть:
так и некоторые более старые версии GCC имеют некоторую поддержку для статической аннотации свойств безопасности потоков. Последовательное использование этого метода превращает множество классов ошибок безопасности потоков в ошибки времени компиляции. Аннотации обычно локальны (отмечают конкретный элемент данных как охраняемый конкретным mutex'ом), и обычно легко изучаются. Однако, как и со многими статическими инструментами, они часто могут давать ложноотрицательные результаты; случаи, которые должны были быть пойманы, но были разрешены.
- Инструменты статической проверки: как clang
является мощным примером динамических инструментов: он изменяет сборку и выполнение вашей программы, чтобы добавить учёт доступа к памяти, однозначно выявляя data race'ы при данном выполнении вашего двоичного файла. Стоимость этого — как память (5-10x в большинстве случаев), так и замедление CPU (2-20x). Динамические инструменты, как этот, лучше всего применяются к тестам интеграции, canary push'ам или модульным тестам, которые работают с несколькими потоками. Производительность имеет значение: Когда TSAN выявляет проблему, это практически всегда фактический data race, но он может только выявить race'ы, видимые при данном выполнении.
- инструменты динамической проверки: Thread Sanitizer (он же TSAN) компилятора Clang
Применение
Итоговый разработчик приложения может выбрать, какие инструменты поддержки ценны для конкретного приложения.
CP.con: Concurrency
Этот раздел сосредоточен на относительно ad-hoc использовании нескольких потоков, взаимодействующих через общие данные.
- Для параллельных алгоритмов см. parallelism
- Для взаимодействия между задачами без явного общего доступа см. messaging
- Для vector параллельного кода см. vectorization
- Для lock-free программирования см. lock free
Резюме правил параллелизма:
- CP.20: Используйте RAII, никогда простой
lock()/unlock() - CP.21: Используйте
std::lock()илиstd::scoped_lockдля получения несколькихmutexов - CP.22: Никогда не вызывайте неизвестный код во время удержания блокировки (например, callback)
- CP.23: Думайте о присоединяемом
threadе как об области видимости контейнера - CP.24: Думайте о
threadе как о глобальном контейнере - CP.25: Предпочитайте
gsl::joining_threadвместоstd::thread - CP.26: Не
detach()'ьте поток - CP.31: Передавайте небольшие объёмы данных между потоками по значению, а не по ссылке или указателю
- CP.32: Для совместного владения между несвязанными
threadами используйтеshared_ptr - CP.40: Минимизируйте переключение контекста
- CP.41: Минимизируйте создание и уничтожение потоков
- CP.42: Не
waitбез условия - CP.43: Минимизируйте время, проведённое в критической секции
- CP.44: Помните об именовании ваших
lock_guardов иunique_lockов - CP.50: Определите
mutexвместе с данными, которые он охраняет. Используйтеsynchronized_value<T>где возможно - ??? когда использовать spinlock
- ??? когда использовать
try_lock() - ??? когда предпочесть
lock_guardвместоunique_lock - ??? Time multiplexing
- ??? когда/как использовать
new thread