Периодическая посылка сообщений

от автора

Пост про эрланг, но применим и ко всем другим языкам.

Пускай мы хотим раз в секунду что-то сделать, например проверить наличие файлика по 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *