Сегодня мы выкатили небольшое обновление для нашей библиотеки для работы с таймерами -- timertt. Изменений в ней совсем мало, поэтому никаких громких анонсов мы не делаем.
Во-первых, мы добавили макрос TIMERTT_VERSION, которых хранит в себе версию библиотеки. В десятичном формате YXXXZZZ, где Y -- это мажорный номер версии, X и Z -- минорный номер и номер патча с лидирующими нулями. Т.е. версия 1.2.1 кодируется как 1002001, а версия 1.3.14 будет кодироваться как 1003014. Каюсь, такой макрос нужно было ввести еще в 1.2.0, но хорошая мысля, как говорится, приходит опосля...
Во-вторых, добавились методы reschedule, которые позволяют переназначить таймер на другое время всего одной операцией. Так, ранее нужно было делать:
auto t = tt.allocate(); tt.activate(t, std::chrono::seconds(30), []{...}); ... // Timer needs to be rescheduled. // Deactivate it first. tt.deactivate(t); // Reactivate it. tt.activate(t, std::chrono::seconds(45), []{...}); |
В случае, если использовался thread-safe механизм (например, такой, как timer_thread), то эта двойная операция обрабатывалась не очень эффективно -- требовалось два раза захватывать mutex.
Теперь же reschedule выполняет это все внутри себя захватив mutex всего один раз:
auto t = tt.allocate(); tt.activate(t, std::chrono::seconds(30), []{...}); ... // Timer needs to be rescheduled. tt.reschedule(t, std::chrono::seconds(45), []{...}); |
Что может быть заметно эффективнее в сценариях, где переназначение таймера используется очень активно.
Однако, с reschedule все не так просто, поэтому мы пока рассматриваем данную функциональность как экспериментальную. Дело в том, что если переназначаемый таймер уже активирован, то нет гарантии, что он будет успешно деактивирован. Подробнее проблемы с деактивацией таймеров описаны здесь, но вкратце суть в следующем: есть стадия обработки таймеров, когда сработавшие таймеры перемещаются в отдельное хранилище, после чего начинается вызов заданных для таймеров действий. Так вот, пока сработавший таймер находится в этом специальном хранилище, деактивировать и переактивировать его нельзя. Использование хранилища -- это цена за высокую скорость обработки таймеров и масштабируемость. Если в reschedule будет передан таймер, который сейчас находится в процессе обработки (т.е. он уже в специальном хранилище), то reschedule выбросит исключение.
Поэтому reschedule следует использовать с большой осторожностью. Например, достаточно безопасно вызывать reschedule при работе с однопоточными timer_manager-ами. Там вы сами определяете, когда вы вызываете process_expired_timers. Соответственно, если вы находитесь вне process_expired_timers, то reschedule вызывать безопасно. А вот внутри process_expired_timers, т.е. внутри таймерного события, reschedule вызывать нельзя.
Так что еще раз: при работе с reschedule нужно проявлять двойную осмотрительность.
Вообще, в timertt с самого начала существует рекомендация: вместо реактивации таймеров лучше создавать таймеры заново. Т.е. сперва вы вызваете deactivate для старого таймера, затем создаете новый таймер и вызываете для него activate. С учетом того, что timertt может поддерживать темп активации в несколько миллионов таймеров в секунду, мы считаем, что это приемлемый подход. Однако, если кому-то приходится сталкиваться с ситуациями, когда прямо внутри deactivate нужно точно знать, что таймер полностью деактивирован (может быть даже он успел сработать прямо внутри deactivate), то дайте знать. Будем думать, как это побороть. Но не бесплатно. Ну, в том смысле, что это не сможет не сказаться на скорости работы timertt. Хотя, если кто-то возьмется проспонсировать такую доработку, мы тоже не откажемся ;)