Пускай мы хотим раз в секунду что-то сделать, например проверить наличие файлика по HTTP урлу. Процесс для этого мы стартовали, надо что бы он что-то делал достаточно регулярно.
В erlang есть три интересующих нас функции: timer:send_interval, timer:send_after и erlang:send_after.
Сначала объясню, почему нельзя пользоваться send_interval.
timer:send_interval проблемен тем, что он шлет сообщения не проверяя, обработано ли предыдущее. В итоге если выполнение задачи начинает залипать, то наш процесс только и занимается что этой задачей. В плохом случае начинается накапливание сообщений в очереди, утечка памяти и полная потеря отзывчивости процесса.
Я неоднократно наблюдал несколько сотен сообщений check в message_queue процесса.
Помимо этого начинается изнасилование внешнего ресурса: жесткого диска или удаленного сервера. Ведь вместо того, что бы делать перерыв в секунду, его может не быть вообще.
Итак, правильный шаг — перепосылать самому себе сообщение в конце обработки задачи.
Между timer:send_after и erlang:send_after выбор очевиден: erlang:send_after. Модуль timer делает это достаточно неоптимально и много таймеров начинают создавать проблемы.
Единственная причина не пользоваться erlang:send_after — это когда процессов много тысяч и можно группировать рассылку сообщений, не загружая систему непростыми в обработке таймерами, но это крайне редкая ситуация.
Однако тут легко допустить ошибку:
init([]) -> self() ! check, {ok, state}. handle_info(check, State) -> do_job(State), erlang:send_after(1000, self(), check), {noreply, State}.
Что будет, если кто-то ещё пошлет в процесс сообщение check? Он породит вторую волну, ведь каждое сообщение check приводит к гарантированной перепостановке таймера.
В итоге, если послать 20 сообщений, то у процесса в каждый момент времени будет 20 таймеров, количество которых не уменьшится.
Правильный ответ такой:
init([]) -> Timer = erlang:send_after(1, self(), check), {ok, Timer}. handle_info(check, OldTimer) -> erlang:cancel_timer(OldTimer), do_task(), Timer = erlang:send_after(1000, self(), check), {noreply, Timer}.
Явное снятие таймера приводит к тому, что если послать 1000 сообщений, то они все обработаются и после этого процесс быстро нормализуется и останется только одна волна перепосылки сообщений.
ссылка на оригинал статьи http://habrahabr.ru/post/155173/
Добавить комментарий