По возможности используйте инструменты для проверки вашего конкурентного кода
Опыт показывает, что конкурентный код исключительно сложно написать правильно и что проверка во время компиляции, проверка во время выполнения и тестирование менее эффективны при поиске ошибок параллелизма чем при поиске ошибок в последовательном коде. Тонкие ошибки параллелизма могут иметь драматически плохие эффекты, включая повреждение памяти, deadlock'и и уязвимости безопасности.
Пример
???Примечание
Безопасность потоков — это сложная задача, часто побеждающая опытных программистов: инструменты — это важная стратегия для смягчения этих рисков. По всему миру существует много инструментов, как коммерческих, так и инструменты с открытым исходным кодом, как исследовательские, так и производственные инструменты. К сожалению, потребности и ограничения людей различаются так сильно, что мы не можем дать конкретные рекомендации, но мы можем упомянуть:
- Инструменты статической проверки: как clang так и некоторые более старые версии GCC имеют некоторую поддержку для статической аннотации свойств безопасности потоков. Последовательное использование этого метода превращает множество классов ошибок безопасности потоков в ошибки времени компиляции. Аннотации обычно локальны (отмечают конкретный элемент данных как охраняемый конкретным mutex'ом), и обычно легко изучаются. Однако, как и со многими статическими инструментами, они часто могут давать ложноотрицательные результаты; случаи, которые должны были быть пойманы, но были разрешены.
- инструменты динамической проверки: Thread Sanitizer (он же TSAN) компилятора Clang является мощным примером динамических инструментов: он изменяет сборку и выполнение вашей программы, чтобы добавить учёт доступа к памяти, однозначно выявляя data race'ы при данном выполнении вашего двоичного файла. Стоимость этого — как память (5-10x в большинстве случаев), так и замедление CPU (2-20x). Динамические инструменты, как этот, лучше всего применяются к тестам интеграции, canary push'ам или модульным тестам, которые работают с несколькими потоками. Производительность имеет значение: Когда TSAN выявляет проблему, это практически всегда фактический data race, но он может только выявить race'ы, видимые при данном выполнении.
Применение
Итоговый разработчик приложения может выбрать, какие инструменты поддержки ценны для конкретного приложения.
<a name="sscp-con"></a>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