Последние сутки чиним довольно специфический баг прямо в продакшене.
Вопрос на засыпку: как долго работает вот такой код:
for (i=0; i<1000000; i++)Казалось бы ответ очевиден - миллион миллисекунд или около 15 минут. НЕТ.
thread.sleep(1);
Правильный ответ - больше 4 часов.
Wtf?! Cейчас я все объясню)
--
У нас на бекенде есть бекграунд-воркер - огромный цикл
while, который бежит по массиву с миллионами элементов и делает с ними всякую арифметику.Но мы ведь не хотим, чтобы во время этого цикла CPU встал колом на 100% и сервер затупил. Мы хотим, чтобы сервер продолжал жить и работать, "принял себя и стал счастливым".
И что делает среднестатистический Петя-программист? Правильно - Петя радостно втыкает в цикл небольшую паузу и идет домой спать.
(если меня читает хоть один геймдев, то на этом месте он унизительно захихикал и достал попкорн)
"Паузы", они же
Delay(), они же Sleep() в большинстве операционных систем сделаны на таймерах. Разрешение этих таймеров - 12-15мс. Сделать паузу на 1 миллисекунду нельзя - будет минимум 15. На большом массиве с миллионом элементов получаем
15ms * 1000000 / 1000 / 60 / 60 = 4.16 - больше четырех часов. И придя утром на работу Петя видит что? Что его цикл все еще работает - медленно и печально. Джоб, который раньше занимал 7 минут (хоть и грузил проц), теперь работает, сука, полсуток. Но зато на релаксе.
Все сломалось и клиенты уже ставят тикеты в стиле "алебля?!"
(а наш геймдев злобно ржет в голос)
Потому что у них в геймдеве это случается примерно все время. И называется "tight loop/busy loop". И на таймеры полагаться нельзя.
Как быть, куды бечь?
1) Использовать multimedia-таймеры или таймеры из openGL/directX (оверкилл)
2) Троттлиться не на каждом шаге, а раз в N итераций (неэлегантно и костыль)
3) Забить на паузы и юзать волшебную инструкцию
Thread.Yield - это такой "вежливый" способ поделиться ресурсами и сказать операционной системе "хей, я все еще занят, но если прям накипело - притормози меня и дай другим поработать" (это лучший способ).Загрузка в 100% никуда не денется, но на нее будет плевать - все будет ворочаться, а сервер будет быстрым и отзывчивым.
Thread.Yield есть во всех языках:C#:
Thread.Yield
C++: std::this_thread::yield
Win32: SwitchToThread
Java: Thread.yield
Go: runtime.Gosched (кажется)Visual Basic:
DoEvents (шутка! ...хотя не совсем)Python:
time.sleep(0) (на винде time.sleep(0.0001) не спрашивайте, почему... потому что Питон...)(кстати, не только Питон - довольно многие системные либы понимают, что "sleep(0)" это на самом деле "yield", включая дотнет, posix и winapi)
и тп.
И напоследок гениальный видос, который я уже кидал в наш уютный чатик. Это я вчера: