Для предстоящего проекта с node.js мне нужно периодически выполнять различные задачи по обслуживанию. В частности, некоторые задачи выполняются каждую миллисекунду, другие — каждые 20 мс (50 раз в секунду), третьи — каждую секунду. Поэтому я подумал об использовании setInterval() с забавными результатами: многие вызовы функций были пропущены.
Тест, который я использовал, выглядит следующим образом:
var counter = 0;
var seconds = 0;
var short = 1;
setInterval(function() {
counter ++;
}, short);
setInterval(function() {
seconds ++;
log('Seconds: ' + seconds + ', counter: ' +
counter + ', missed ' +
(seconds * 1000 / short - counter));
}, 1000);
Есть длинный таймер на одну секунду и короткий, который можно настроить с помощью переменной short
, в данном случае 1 мс. Каждую секунду мы печатаем разницу между количеством ожидаемых тиков в коротком цикле и фактическим количеством обновлений короткого счетчика.
Вот как он ведет себя, когда короткий таймер равен 1 мс:
2012-09-14T23:03:32.780Z Seconds: 1, counter: 869, missed 131
2012-09-14T23:03:33.780Z Seconds: 2, counter: 1803, missed 197
2012-09-14T23:03:34.781Z Seconds: 3, counter: 2736, missed 264
...
2012-09-14T23:03:41.783Z Seconds: 10, counter: 9267, missed 733
Многие вызовы функций пропускаются. Вот это для 10 мс:
2012-09-14T23:01:56.363Z Seconds: 1, counter: 93, missed 7
2012-09-14T23:01:57.363Z Seconds: 2, counter: 192, missed 8
2012-09-14T23:01:58.364Z Seconds: 3, counter: 291, missed 9
...
2012-09-14T23:02:05.364Z Seconds: 10, counter: 986, missed 14
Лучше, но примерно один вызов функции пропускается каждую секунду. И за 20 мс:
2012-09-14T23:07:18.713Z Seconds: 1, counter: 46, missed 4
2012-09-14T23:07:19.713Z Seconds: 2, counter: 96, missed 4
2012-09-14T23:07:20.712Z Seconds: 3, counter: 146, missed 4
...
2012-09-14T23:07:27.714Z Seconds: 10, counter: 495, missed 5
Наконец, для 100 мс:
2012-09-14T23:04:25.804Z Seconds: 1, counter: 9, missed 1
2012-09-14T23:04:26.803Z Seconds: 2, counter: 19, missed 1
2012-09-14T23:04:27.804Z Seconds: 3, counter: 29, missed 1
...
2012-09-14T23:04:34.805Z Seconds: 10, counter: 99, missed 1
В этом случае он пропускает очень мало вызовов (разрыв увеличился до 2 через 33 секунды и до 3 через 108 секунд).
Цифры различаются, но на удивление совпадают между запусками: запуск первого теста 1 мс три раза дал задержку после 10 секунд 9267, 9259 и 9253.
Я не нашел ссылок на эту конкретную проблему. Есть этот часто цитируемый пост Ressig и множество вопросов по JavaScript, но большинство предполагает, что код работает в браузере, а не в node.js.
А теперь самый неприятный вопрос: что здесь происходит? Просто шутка; очевидно, вызовы функций пропускаются. Но я не вижу закономерности. Я думал, что длинные циклы могут мешать коротким, но в случае с 1 мс это не имеет никакого смысла. Вызовы функций с коротким циклом не перекрываются, поскольку они просто обновляют переменную, а процесс node.js потребляет около 5% ЦП даже при коротком цикле в 1 мс. Однако средняя нагрузка высока, около 0,50. Я не знаю, почему тысяча вызовов так сильно нагружает мою систему, поскольку node.js обрабатывает намного больше клиентов; должно быть верно, что setInterval() интенсивно использует ЦП (или я делаю что-то не так).
Очевидным решением является группировка вызовов функций с использованием более длинных таймеров, а затем многократное выполнение вызовов функций с коротким циклом для имитации более короткого таймера. Затем используйте длинный цикл в качестве «вагона с метлой», который пропускает любые вызовы в более низких интервалах. Пример: настройте вызовы setInterval() на 20 мс и 1000 мс. Для вызовов 1 мс: вызовите их 20 раз в обратном вызове 20 мс. Для вызова 1000 мс: проверьте, сколько раз была вызвана функция 20 мс (например, 47), выполните оставшиеся вызовы (например, 3). Но эта схема будет немного сложной, поскольку вызовы могут интересным образом перекрываться; также это не будет регулярным, хотя это может выглядеть так.
Реальный вопрос: можно ли это сделать лучше с помощью setInterval() или других таймеров в node.js? Заранее спасибо.