Как не нужно фиксить concurrency проблемы
— Ваша сильная сторона?
— Multithreading
— Вот 3 задачки. Сможете сделать до завтра?
— Я не могу решать столько задач одновременно
Согласитесь, сoncurrency — одна из непростых тем программирования. В начале своей карьеры программиста кто‑то всячески пытается избежать погружения в эту тему, но рано или поздно приходится столкнуться с concurrency проблемами. Это может случиться, потому что нужно написать потокобезопасный код или прилетел баг на уже имеющийся код.
По неопытности некоторые могут просто замаскировать проблему, которая позже снова даст о себе знать. Некоторые из этих подходов распишу ниже. Но имейте в виду, это просто временное решение, которое на самом деле не решает проблему сoncurrency.
Статья не про серебряную пулю, как исправить concurrency проблемы, а про костыльные подходы веселых и находчивых, которые не решают суть проблемы.
Sleep — вместо тысячи слов
Дело в том, что Sleep добавляют не только начинающие программисты. Мне встречались программисты, которые при опыте 3–5 лет работы делали такой фикс и в тестах кода, и в продакшене. Подобный код является по своей сути бомбой замедленного действия. Важно понять одно: если вы делаете фиск такого рода, он может быть замаскирован до поры до времени. Потом пройдёт обновление операционной системы или же вы поменяете железо на новое и все, тайминги изменятся и старые проблемы станут явными.
Обратите внимание, увеличение времени sleep‑а — тоже не выход:)
Подход «Логов, больше логов» маскирует concurrency проблему
Порой обнаружение корня проблемы занимает много времени, поэтому программисты на практике часто добавляют логи для дальнейшего изучения проблемы, что правильно. Можно добавить немного логов — это действительно полезно и помогает решению проблемы. Но, бывает, добавляют лог‑сообщения через строчку. У вас там маленький кусок кода, обложенный кучей логов, и код чудесным образом перестает падать. Потом через какое‑то время поднимается история бага, и оказывается, что он не воспроизводился некоторое время. Поэтому баг закрывают по причине «Не воспроизводится N месяцев, возможно, уже пофиксили». А на деле баг остался.
Давайте разберемся, почему это пока «работает». В местах добавления логов код начинает выполняться медленнее и создаёт иллюзию, что рассинхрона нет и все «заработало», но ничего подобного. Нужно всегда помнить, input/output операции дорого стоят с точки зрения ресурсов системы.
Try‑catch как фикс
Оборачивать все в try‑catch конструкции вместо «нормального» фикса — тоже не выход. Осознанное использование этого подхода в продакшене я видела лишь однажды.
Дело было так. Жил‑был непотокобезопасный код много лет, который использовался в разных частях проекта. Время от времени появлялись креш‑дампы из‑за отсутствия средств синхронизации потоков в коде, который многие использовали как потокобезопасный. Со временем код стал устаревать, его начали заменять в проектах, и получил этот непотокобезопасный код статус «устаревший» и кандидат на удаление. И вот код уже встречается только в одном месте, креш‑дампы естественно продолжают появляться. Тикеты на проблему накапливаются, и менеджеры просят пофиксить проблему с условием минимальных затрат по времени. Переписывать тысячи строк кода, чтобы синхронизовать устаревший код, который удалят через 2–3 месяца, бессмысленно, только время потратишь. Решением категории «дёшево и сердито» оказывается обернуть соответствующие методы в try‑catch. Все понимают, что это корявое решение проблемы, но ничего не поделаешь, если желательно исправить сегодня. По факту, это даже не фикс, а маскирующий костыль. Делать подобное в поддерживаемом коде нельзя.
Давайте повторим N раз до победного
Бывает, какая‑то часть боевого кода или тест периодически падает. Быстро найти проблему не удается, да и проблема не совсем очевидная. Тут приходит гениальная мысль обернуть код в try‑catch, но не просто ловить исключения, а повторять падающий код несколько раз до победного конца. Такой подход напоминает русскую рулетку: выстрелит — не выстрелит. Результат выполнения кода вроде как улучается: количество падений уменьшается. Такой подход дает только временный эффект. Ведь реальная проблема не решена, и в какой‑то момент частота воспроизведения проблемы снова возрастет, и вы возвращаетесь к тому, с чего начали.
Как ни крути, при разных подходах маскировки concurrency проблемы, ее все равно придется решать.