Монотонное время — субъективное время в Системе, для которого характерно отсутствие резких скачков вперёд или назад, вызванное переводом компьютерных часов.
В системном времени могут возникать скачки назад. Например, подобные:
...
1769217271
1769217272
1769217270
1769217271
1769217272
1769217273
...
Монотонное время лишено этого недостатка. У него никогда не будет так, что очередное значение времени меньше предыдущего.
Чтобы узнать текущее значение монотонного времени, есть биф erlang:monotonic_time(Unit). Монотонное время нужно в первую очередь для определения интервалов между двумя моментами времени, поэтому этот биф возвращает некое отрицательное число (а не POSIX-время). Если время исчисляется в секундах, это число через каждую секунду увеличивается на 1.
1> erlang:monotonic_time().
-576460745717135773
2> erlang:monotonic_time(second).
-576460740
3> erlang:monotonic_time(second).
-576460735
Проведём такой эксперимент. У нас есть компьютер, который уже два дня не был подключён к интернету и поэтому не синхронизировал своё время. Подключимся — и посмотрим, как изменится разница между системным временем и монотонным.
80> os:system_time(microsecond) - erlang:monotonic_time(microsecond).
2345669957202995
81> os:system_time(microsecond) - erlang:monotonic_time(microsecond).
2345669957202994
82> os:system_time(microsecond) - erlang:monotonic_time(microsecond).
2345669957202995
83> os:system_time(microsecond) - erlang:monotonic_time(microsecond).
2345669957202995
84> os:system_time(microsecond) - erlang:monotonic_time(microsecond).
2345669957202995
85> os:system_time(microsecond) - erlang:monotonic_time(microsecond).
2345669961388391
86> os:system_time(microsecond) - erlang:monotonic_time(microsecond).
2345669961388391
87> os:system_time(microsecond) - erlang:monotonic_time(microsecond).
2345669961388390
88> os:system_time(microsecond) - erlang:monotonic_time(microsecond).
2345669961388391
89> os:system_time(microsecond) - erlang:monotonic_time(microsecond).
2345669961388390
90> os:system_time(microsecond) - erlang:monotonic_time(microsecond).
2345669961388390
91> os:system_time(microsecond) - erlang:monotonic_time(microsecond).
2345669961388390
92> os:system_time(microsecond) - erlang:monotonic_time(microsecond).
2345669961388390
93> os:system_time(microsecond) - erlang:monotonic_time(microsecond).
2345669961388391
После шага 84 произошла синхронизация. До этого разница между системным и монотонным временем была стабильной. Важно отметить, что и после синхронизации разница остаётся стабильной — до следующей синхронизации системных часов с глобальными. Можно было предположить, что после синхронизации течение монотонного времени ускорится или замедлится, дабы сохранить некую константу разницы с системным временем, но этого не произошло.
Поэтому если измерять время работы нашей программы по системному и монотонному времени, разница будет всё больше и больше. Через неделю, допустим, эта разница составит 1 секунду, через две недели — 2 секунды и т.д. Скорость нарастания этой разницы зависит от точности компьютерных часов. От того, как часто осуществляется синхронизация времени, это не зависит.
Впрочем, это всего лишь один из возможных (но дефолтный) режимов работы монотонного времени. Есть и другие.
Все таймеры в Эрланге срабатывают относительно монотонного времени. Не может быть такой ситуации, что мы запланировали что-то сделать через 3 секунды, а этого не произошло из-за синхронизации времени. Ну и, конечно, не может быть такого, что запланированное событие случилось дважды из-за скачка времени.
И тут возникает довольно непростой вопрос. Допустим, мы желаем, чтобы через полгода случилось какое-то событие, причём с точностью до секунды или, даже лучше, с точностью до миллисекунды. Это событие должно случиться в момент по системному времени, а не монотонному. Мы, может быть, просто создадим актор, который каждую миллисекунду будет проверять системное время. Это не сильно нагрузит систему (узнать время обходится в 1-2 микросекунды).
Есть и другой вариант. Можно использовать erlang:start_timer. Он в качестве аргумента может принимать абсолютное монотонное время. С помощью erlang:time_offset мы находим сдвиг монотонного времени относительно системного, вычисляем желаемое монотонное время — и запускаем таймер. Однако через полгода time_offset может быть совсем другой, и тогда таймер сработает не в тот момент, когда нам надо. Чтобы узнать, когда время сдвинулось можно задействовать монитор erlang:monitor(time_offset, clock_service). Актор получит сообщение о новом сдвиге времени. Получив это сообщение, он должен остановить работающий таймер и запустить его с новым параметром времени.
Использование функций для определения времени зависит от задач. Если задача заключается в том, чтобы узнать системное время Эрланга в данный момент, для этого используется erlang:system_time. Если надо надёжно измерять разницу двух моментов времени, для этого лучше использовать erlang:monotonic_time.
Если задача заключается лишь в корректном определении последовательности событий, то лучшим вариантом будет erlang:unique_integer([monotonic]). Редко, но бывает, что биф erlang:monotonic_time(Unit) даёт одинаковое время для двух событий (когда монотонные часы ненадолго замирают при откатывании системных часов назад). Поэтому лучше использовать генератор уникальных чисел, которые возрастают монотонно.
Если стоит задача и зафиксировать время момента, и очерёдность событий, для этого лучше использовать кортеж. Первым элементом будет время, возвращаемое erlang:monotonic_time, а вторым — уникальное число, генерируемое erlang:unique_integer([monotonic]). Очерёдность должна быть именно такая: если у двух моментов окажется одинаковое монотонное время, тогда произойдёт сравнение по второму элементу кортежа. Можно добавить и третий элемент — сдвиг времени (монотонного относительно системного), т.е. результат функции erlang:time_offset.
Если для какого-то момента времени мы знаем его монотонное время и сдвиг, то надо просто сложить эти два значения, чтобы получить системное время.
1> Mono = erlang:monotonic_time(second).
-576460736
2> Offset = erlang:time_offset(second).
2345700255
3> Mono + Offset.
1769239519
4> erlang:system_time(second).
1769239561
Если стоит задача присвоить каждому событию уникальное имя, опять же можно использовать для этих целей erlang:unique_integer. Если добавить опцию positive, уникальные числа будут начинаться с одного:
1> erlang:unique_integer([monotonic,positive]).
1
2> erlang:unique_integer([monotonic,positive]).
2
3> erlang:unique_integer([monotonic,positive]).
3
Copyright © 2026 Алексей Карманов