Senior PHP Developer
0,0
рейтинг
28 января 2011 в 19:19

Разработка → Асинхронное программирование, коллбеки и использование process.nextTick()

Несмотря на то, что большинство из изучающих Node.js в какой-то мере знают JavaScript и имеют опыт использования его в контексте браузеров, при обсуждении практических моментов многие встречаются с трудностями в понимании работы стандартной библиотеки и механизмов обеспечения асинхронного выполнения кода, содержащего множество вложенных коллбеков. Также часто возникает недопонимание, Я постараюсь вкратце описать порядок работы event loop в Node.js и рассказать, на какие моменты стоит обратить внимание при написании качественного асинхронного кода. Думаю, что статья будет полезна и тем, кто занимается написанием производительных фреймворков для браузеров.



Лирическое отступление: Цикл событий, лежащий в основе Node.js



Как уже много раз было написано, в основе Node.js лежит цикл событий, реализуемый библиотекой libev. На каждом витке цикла происходит следующее: в первую очередь идёт выполнение функций, установленных на предыдущем витке цикла с помощью process.nextTick(). Далее идёт обработка событий libev, в частности событий таймеров. В последнюю очередь идёт опрос libeio для завершения операций ввода/вывода и выполнения установленных для них коллбеков. В случае, если при прохождении цикла оказалось, что ни одна функция не установлена с помощью process.nextTick(), нет ни одного таймера и очереди запросов в libev и libeio пусты, то node завершает работу. Если вы хотите подробнее узнать порядок работы event loop, собетую пролистать презентацию www.slideshare.net/jacekbecela/introduction-to-nodejs.

Таким образом, если сервер содержит мало логики и занимается в основном малозатратной по времени обработкой приходящих данных, то цикл выполняется очень часто. Однако, если обработка данных происходит долго, то целесообразно разделить этот процесс на отдельные части, между которыми возвращать управление в цикл событий для возможности запуска обслуживания нового соединения или обработки асинхронно читаемых с диска данных. Рассмотрим пример HTTP-сервера: при обращению к нему он читает из текущей папки файл с именем, соответствующим строке запроса и возвращает определённую хеш-сумму от его содержимого.

Синхронная версия тестового сервера



    // readFileSync.js
    var
      http = require('http'),
      fs = require('fs');
    
    function func1(str) {
      var res = '';
      for (var i = 0, l = str.length; i < l; i++) {
        res += str.charCodeAt(i);
      }
      return res;
    }
    
    function func2(str) {
      var res = 0;
      for (var i = 0, l = str.length; i < l; i++) {
        res += Math.sin(str.charCodeAt(i));
      }
      return '' + res;
    }
    
    http.createServer(function (req, res) {
      // Very simple and dangerous check
      var filename = req.url.replace(/\?.*/, '').replace(/(\.\.|\/)/, '');
      
      // Read file from disk
      try {
        var filecontent = fs.readFileSync(filename, 'utf8');
      } catch (e) {
        res.writeHead(404, {'Content-Type': 'text/plain'});
        res.end('File ' + filename + ' doesn\'t exist');
        return;
      }
      
      // Calculate checksum
      var hash = func2(func1(filecontent));
      
      // Write response
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.end(hash);
    }).listen(8124, "127.0.0.1");


Пример сервера содержит синхронное чтение файла с диска, которое блокирует выполнение до завершения чтение, и последующее вычисление значение двух функций, которые могут долго выполняться при большом размере файла. При этом если чтение занимает Tread секунд, а вычисление суммы Tcalc секунд, то такой блокирующий сервер сможет обслужить меньше чем 1/(Tread + Tcalc) запросов в секунду. Как можно улучшить наш сервер, позволив ему обрабатывать большее количество соединений? В первую очередь, использовать неблокирующее чтение файла.

Асинхронное чтение файла и попытка использовать коллбеки



    // readFile.js
    
    var
      http = require('http'),
      fs = require('fs');
    
    function func1(str) {
      var res = '';
      for (var i = 0, l = str.length; i < l; i++) {
        res += str.charCodeAt(i);
      }
      return res;
    }
    
    function func2(str) {
      var res = 0;
      for (var i = 0, l = str.length; i < l; i++) {
        res += Math.sin(str.charCodeAt(i));
      }
      return '' + res;
    }
    
    http.createServer(function (req, res) {
      // Very simple and dangerous check
      var filename = req.url.replace(/\?.*/, '').replace(/(\.\.|\/)/, '');
      
      // Read file from disk
      fs.readFile(filename, 'utf8', function (err, filecontent) {
        if (err) {
          res.writeHead(404, {'Content-Type': 'text/plain'});
          res.end('File ' + filename + ' doesn\'t exist');
          return;
        }
        
        // Calculate checksum
        var hash = func2(func1(filecontent));
        
        // Write response
        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.end(hash);
      });
    }).listen(8124, "127.0.0.1");


>За счёт использование асинхронного чтения мы можем добиться ускорение обработки каждого запроса за счёт того, что во время вычислений в фоном режиме будет происходить чтение файла для другого запроса. <strikeТаким образом, время обработки будет равняться min(Tread, Tcalc), а не (Tread + Tcalc), как в случае синхронного сервера. Таким образом, при поступлении двух запросов в синхронном случае время их обслуживания будет равняться Tread1 + Tcalc1 + Tread2 + Tcalc2, а при асинхронном чтении оно может достигать Tread1 + Tread2 + min(Tcalc1, Tcalc2).

Это уже хорошо. Но что делать, если время обработки файла сильно больше времени чтения файла и кроме того сильно флуктуирует? В этом случае за время вычисления суммы для одного файла могут успеть прочитаться несколько других файлов меньшего размера, которые впоследствии быстро обработаются. Кроме того, в приведённом пример клиенты получат результат практически в том же порядке, в котором были посланы запросы к серверу. Однако логично желание вернуть результат раньше тем клиентам, которые запрашивают меньшие файлы или файлы, требующие меньше времени на обработку. Для этого при использовании длинной цепи вложенных функций обработки необходимо каким-то образом после вычисления func1() вернуть управление в основной поток, а на следующем витке цикла вычислить func2() и вернуть результат клиенту. За счёт этого в промежутке между вычислением func1() и func2() для одного запроса может произойти принятие нового соединения и создания задания на чтение другого файла, или обработка уже считанного файла меньшего размера.

Как в таком случае поступают новички в Node.js (на самом деле их стоит называть новичками в JavaScript, потому что это касается использования языка в любой из распространённых JavaScript VM)? Так как асинхронные функции ввода/вывода из стандартной библиотеки возвращают выполнение в основной поток сразу же после вызова, то многие считают, что достаточно написать функцию, принимающую коллбек, и она будет обеспечивать в месте своего вызова разрыв в основном потоке выполнения.

    // readFile-and-sync-chain.js
    
    var
      http = require('http'),
      fs = require('fs');
    
    function func1(str) {
      var res = '';
      for (var i = 0, l = str.length; i < l; i++) {
        res += str.charCodeAt(i);
      }
      return res;
    }
    
    function func2(str) {
      var res = 0;
      for (var i = 0, l = str.length; i < l; i++) {
        res += Math.sin(str.charCodeAt(i));
      }
      return '' + res;
    }
    
    function func1_cb(str, cb) {
      var res = func1(str);
      
      cb(res);
    }
    
    function func2_cb(str, cb) {
      var res = func2(str);
      
      cb(res);
    }
    
    http.createServer(function (req, res) {
      // Very simple and dangerous check
      var filename = req.url.replace(/\?.*/, '').replace(/(\.\.|\/)/, '');
      
      // Read file from disk
      fs.readFile(filename, 'utf8', function (err, filecontent) {
        if (err) {
          res.writeHead(404, {'Content-Type': 'text/plain'});
          res.end('File ' + filename + ' doesn\'t exist');
          return;
        }
        
        // Calculate checksum
        func1_cb(filecontent, function (str) {
          func2_cb(str, function (hash) {
            // Write response
            res.writeHead(200, {'Content-Type': 'text/plain'});
            res.end(hash);
          });
        });
      });
    }).listen(8124, "127.0.0.1");


Что же произойдёт на самом деле? Никакой магии, конечно же, нет. Разница будет заключаться только в том, что чисто императивный код вычисления сумм мы заменили кодом с двумя вложенными функциями-коллбеками, которые будут последовательно вызывать друг друга и немного увеличат время вычисления сумм за счёт лишних вызовов функций, что в конечном счёте только ухудшит производительность нашего сервера.

Асинхронное чтение файла и правильная асинхронная обработка



Для того, чтобы передать управление в основной поток выполнения и при этом поставить на будущее задачу дальнейшей обработки суммы после вычисления func1(), можно использовать старое проверенное средство, доступное в JavaScript: setTimeout(fn, 0). Именно эту функцию стоило бы использовать, если бы мы программировали для браузеров. Но, как я уже писал выше, в Node.js есть функция process.nextTick(fn), которая эффективнее и переданная в неё функция будет выполнена гарантированно раньше, чем функции, установленные с помощью таймеров или являющиеся обработчиками событий от сокетов или файловой системы. Таким образом, код сервера readFile-and-sync-chain.js можно переписать следующим образом:

    // readFile-and-nextTick.js
    
    var
      http = require('http'),
      fs = require('fs');
    
    function func1(str) {
      var res = '';
      for (var i = 0, l = str.length; i < l; i++) {
        res += str.charCodeAt(i);
      }
      return res;
    }
    
    function func2(str) {
      var res = 0;
      for (var i = 0, l = str.length; i < l; i++) {
        res += Math.sin(str.charCodeAt(i));
      }
      return '' + res;
    }
    
    function func1_cb(str, cb) {
      var res = func1(str);
      
      process.nextTick(function () {
        cb(res);
      });
    }
    
    function func2_cb(str, cb) {
      var res = func2(str);
      
      process.nextTick(function () {
        cb(res);
      });
    }
    
    http.createServer(function (req, res) {
      // Very simple and dangerous check
      var filename = req.url.replace(/\?.*/, '').replace(/(\.\.|\/)/, '');
      
      // Read file from disk
      fs.readFile(filename, 'utf8', function (err, filecontent) {
        if (err) {
          res.writeHead(404, {'Content-Type': 'text/plain'});
          res.end('File ' + filename + ' doesn\'t exist');
          return;
        }
        
        // Calculate checksum
        func1_cb(filecontent, function (str) {
          func2_cb(str, function (hash) {
            // Write response
            res.writeHead(200, {'Content-Type': 'text/plain'});
            res.end(hash);
          });
        });
      });
    }).listen(8124, "127.0.0.1");


Этот результат и является конечным вариантом сервера, который выполняет все операции асинхронно и содержит минимальное количество участков кода, блокирующих цикл событий.

Сравнение производительности рассмотренных вариантов



Всё, о чём говорится выше, по большей части рассуждения о правильной архитектуре. На деле производительность того или иного варианта может зависеть от того, какой размер читаемого файла и время его обработки, от нелинейности зависимости вермени обработки от размера файла и от того, наскольок разнообразные запросы обрабатывает сервер. Тем не менее, тесты показали, что в любом случае использования более правильных с точки зрения архитектуры решений даже в худшем случае не замедляет сервер больше, чем на 10%.

Для сравнения использовались файлы размером от 128 байт до 1 Мб и сервер нагружался с помощью Apache Bench:

ab2 -n 1000 -c 100 http://127.0.0.1:8124/filename


Результаты приведены на графиках:


Как видно, использование асинхронного чтение действитьно улучшает производительность сервера при больших размерах файла. Это связано с тем, что при чтении маленьких файлов накладные расходы на создания различных структур для асинхронного чтения превышают время чтения файлов, тем более что они читаются из кеша ФС. Как и ожидалось, использование обычных вложенных функций в не улучшает производительность. А вот использование асинхронных коллбеков при поэтапной обработке файла принесла свои результаты, которые хорошо видны для небольших размеров файлов.

Однако следует отметить, что результаты теста сильно зависят от размера запрашиваемого файла как в лучшую, так и в худшую сторону, а также от разнообразия поступающих серверу запросов. Надеюсь, у меня найдётся время на расширенное тестирование с несколькими файлами и различным количеством запросов для них. Также я намерено не рассматриваю проблемы, связанные с неполной реализацией асинхронного ввода/вывода в некоторых ОС и ограничением на количество потоков, используемых libeio для эмуляции асинхронных операций для таких систем.

P.S. Спасибо nodejs-новичкам с forum.nodejs.ru за поднятие этого вопроса.
Олег Ефимов @Sannis
карма
79,7
рейтинг 0,0
Senior PHP Developer
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (50)

  • +2
    Жуть… Асинхронный сервер с тяжелыми вычислениями

    В случае, если при прохождении цикла оказалось, что ни одна функция не установлена с помощью process.nextTick(), нет ни одного таймера и очереди запросов в libev и libeio пусты, то node завершает работу.
    Node наверное работу не завершает, просто Loop завершается…

    А есть ли в Node способ считывать файл не целиком а по кусочкам? По крайней мере ваши функции хеширования можно применять не ко всему файлу а к его кусочкам с таким же результатом, считывая кусочки файла и хешируя их, так сказать, «в конвейерном режиме».

    Таким образом, время обработки будет равняться min(Tread, Tcalc), а не (Tread + Tcalc)
    Что то не вызывает доверия это утверждение. Что такое время обработки? Через сколько клиент получит ответ? Да через столько же т.к. пока вы файл не загрузите, калькуляции вы над ним не проведете.
    • 0
      Node наверное работу не завершает, просто Loop завершается


      Да, Node в таком случае завершит работу только если не осталось обработчиков (и, соответственно, не могут обрабатываться никакие события).
      • +1
        Имеется в виду не классическое поднятие сервера, просто последовательность javascript команд?
        • 0
          Ну да. Пока есть хотя бы один блок команд который может выполниться (обработчик события либо исходный скрипт), нода не завершится.
          • +1
            О чём я, впрочем, и написал :)
    • +1
      Node наверное работу не завершает, просто Loop завершается…
      Наверное, это вопрос терминологии, считать ли момент ожидания событий честью цикла. По идее, нужно, тогда наши с вами утверждения идентичны, разве нет? :)

      А есть ли в Node способ считывать файл не целиком а по кусочкам? По крайней мере ваши функции хеширования можно применять не ко всему файлу а к его кусочкам с таким же результатом, считывая кусочки файла и хешируя их, так сказать, «в конвейерном режиме».
      Да, можно считывать по частям. Я старался не касаться этого вопроса в статье, обсуждая наиболее часто возникающие вопросы.

      Что то не вызывает доверия это утверждение. Что такое время обработки? Через сколько клиент получит ответ? Да через столько же т.к. пока вы файл не загрузите, калькуляции вы над ним не проведете.
      Согласен с вами, некорректно оценил время. Тем не менее, если представить себе одновременный приход двух запросов, то возможен случай, когда будет инициализировано чтение двух файлов, далее один из них прочитается, обработается и результат вернётся клиенту, а потом будет обработан второй файл. В этом случае второй клиент получит результат раньше, чем при синхронной реализации сервера.
      • 0
        Ну да, по этой части придирки в основном к терминологии.
        • 0
          Я добавил ссылку на англоязычную презентацию, там довольно подробно описан event loop. Думаю кто захочет разобраться — разберётся.
    • 0
      А есть ли в Node способ считывать файл не целиком а по кусочкам? По крайней мере ваши функции хеширования можно применять не ко всему файлу а к его кусочкам с таким же результатом, считывая кусочки файла и хешируя их, так сказать, «в конвейерном режиме».

      Да, есть:
      
      fs.createReadStream('sample.txt', {start: 90, end: 99});
      
  • +2
    Но что делать, если время обработки файла сильно больше времени чтения файла и кроме того сильно флуктуирует?
    В этом случае надо НЕиспользовать асинхронный сервер)))
    • +1
      Расскажите самоучке, почему? На мой взгляд это как раз хороший use-case, так как это позволяет быстрее обслуживать клиентов, для запросов которых не нужны долгие вычисления.
      • +1
        Для запросов для которых НЕ нужны долгие вычисления конечно хороший use-case. Но мой комментарий как раз по поводу «что делать, если время обработки файла сильно больше времени чтения»

        Дело в том, что асинхронный сервер в общем то нужен для долгоживущих соединений (IM сервер, веб-сокет, Long-pool) или если действительно мало работы нужно проделать над соединением.

        А если над каждым запросом нужно пыхтеть считая хеши файлов или генерируя тяжелые веб-странички, то в какой то момент сервер перестанет успевать обрабатывать запросы, очередь запросов и время ожидания вырастет, ну и потом вообще Reject начнется. Упремся в CPU короче.

        А для масштабирования нужно запускать все тех-же воркеров…
        • 0
          Согласен с вами.
  • +1
    О, здорово!
    Побольше бы на хабре новых статей, освещающих пути входа в царство node.js
  • +1
    Почему то мне кажется, что на реальных ситуациях будет уместным загружать файл, который может быть загружен заранее в отдельном потоке, проверять таймаутом изменения в файле (проверять дату изменения или ещё чем-либо), при наличии таковых заново загружать файл.
    При появлении клиента отдавать текущий контент.
    Тогда количество клиентов не будет зависеть от размера обрабатываемого файла.
    • +1
      Даже не потоке, а тамже, возможно даже тупо в лоб загружать в переменную
      var content
      setTimeout(function(){
      if(файл изменён) загрузитьфайл(файл, function() {content = вычислитьхеш()})
      })
      server.(){
      write (content)
      }).listen(8080)
    • +1
      Реальные ситуации в любом случае отличаются от приводимого в статье примера. Своей задачей я в основном ставил сравнения различных способов обработки, так как часто возникают вопросы о том, зачем нужен process.nextTick().

      Что касается кеширования, то тут нужно глубже копаться в libeio, возможно там используются пользовательские функции чтения, которые и так кешируют данные.
      • +1
        Данные (считанный файл) кешируется ОС, для этого каких-то особых действий предпринимать не надо. Тут, если я правильно понял, предложили кешировать не сам файл, а уже посчитанный хеш. Но, имхо, его нужно в файл или в мемкеш сохранять а не внутри сервера… А то понадобится сервер перегрузить и заново придется все считать и хешировать.
  • +3
    «Как видно, использование асинхронного чтение действитьно улучшает производительность сервера при больших размерах файла»

    поднимите мне веки, ничего не вижу
    • +1
      Имеются в виду графики в конце статьи.
      • +2
        уважаю потраченное автором время, но о том и речь — на графике все в пределах погрешностей.
        не говоря уже о том, что почему-то их два :)
  • +2
    Я из графиков вижу что на маленьких файлах быстрее синхронный код, а на больших readFile-and-sync-chain.js
    который не должен быть быстрей readFile.js

    В чем подвох?
    • 0
      Основная проблема теста в том, что запрашивался один и тот же файл. Кроме того я не очень уверен в «чистоте» окружения при проведении тестов.
      В реальной ситуации стоит брать отдельные диапазоны размеров, генерировать несколько файлов каждого размера и посылать запросы на обработку в случайном порядке. Тогда и статистически всё будет честнее, и к реальности ближе. В любом случае, правильная архитектура, имхо, стоит 10% потерь.
      • +1
        А если попробовать брать файл из /dev/random или иного некеширующего источника?
        • +2
          Можно попробовать c /dev/urandom, с /dev/random неохота долго возиться :)
  • НЛО прилетело и опубликовало эту надпись здесь
    • +1
      Тоже не понял смысла вписывать в коллбеки nextTick.
      • +2
        Иногда может быть полезным.
        Например, клиент запросил скрипт, сервер создал запись в логе.
        обычный подход:
        сгенерировать_контент()
        записать_файл()
        отослать_и_закрыть_соединение()

        С nexttick
        сгенерировать_контент()
        nextTick(function(){записать_файл()});
        отослать_и_закрыть_соединение()

        Тогда время реагирования скрипта будет меньше.
        • +1
          ну а разве вот это не то же самое:

          сгенерировать();
          записать_файл_async();
          отослать_и_закрыть();

          ?
          Ведь асинхронные функции вроде для того и нужны, чтобы поток не ждал их выполнения.
          • +1
            В случае, если в цепочке обслуживания запроса есть много «дырок», это позволяет в них начать обрабатывать другой запрос. А если он требует асинхронного ввода/вывода, то мы получим преимущество за счёт того, что запрос на асинхронное чтение запустится раньше.
            • 0
              То есть, вы предлагаете просто искуственно натыкать побольше таких дырок?
              • +1
                Главное не переборщить.
          • +1
            Ну да, просто пример не идеальный.
            Допустим, перед записью лога в файл, его надо ещё долго обрабатывать, тогда можно запихнуть обработки и запись в nextTick, а соединение с клиентом завершить уже сейчас.
        • +1
          сгенерировать_контент()
          отослать_и_закрыть_соединение()
          записать_файл()

          Так вроде тоже можно. в Node передача ответа и закрытие соединения клиенту идет не через return а через res.end(data);
          • +1
            отослать_и_закрыть_соединение() псевдокод, конечно же я имел в виду res.end(контент_сгенерированный_ранее);
            • 0
              не, псевдокод я понял. Имею в виду что можно закрыть соединение с клиентом, отключить его и уже после этого безо всяких nextTick доделать остальную работу, которая не повлияет на отдаваемый клиенту результат (запись в лог и т.д.).
              • 0
                Постарался описать, почему от разрыва в основном потоке выполнения может получиться преимущество и увеличение RPS. Возможно, иллюстрации, которые я сейчас делаю, помогут в этом убедиться или наоборот придумать убедительные аргументы против моей точки зрения.
        • НЛО прилетело и опубликовало эту надпись здесь
    • +1
      За счёт того, что при наличии такого промежутка в вычислениях в него может попасть начало асинхронного чтения, а значит чтение произойдёт раньше и возрастёт отзывчивость. Но, конечно, это работает только для серверов с активным вводом/выводом.
  • +1
    > Но, конечно, это работает только для серверов с активным вводом/выводом.

    > Время максимальной блокировки определяется самым тяжелым вычислением.

    Об этом я и говорю. Если основную часть времени занимает чтение, то это даст прирост, если вычисление длинные — прироста не будет. Само собой ничего не бывает, но в среднем от лишнего прерывания отзывчивость сервера увеличится, особенно если есть запросы с различным временем обработки. Я думаю провести тест в таких условиях в ближайшее время, надеюсь что всё подтвердится.

    Есть, правда, другой вопрос: а не проще ли создать несколько процессов и в каждом обрабатывать запросы синхронно.
    • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        Вторые должны разруливаться асинхронными вызовами — ну это одна из задач ноды.

        Тут есть одна «проблема»: libeio справится с асинхронным чтением, но для этого нужно, чтобы из основного потока поступил запрос на начало чтения. А если будет длительная (но не настолько длительная, чтобы использовать другую архитектуру) обработка файла для первого запроса, то когда бы не пришёл второй запрос, ему придётся ждать пока обработка завершится и только после этого в основном потоке начнётся его обработка и отправка запроса для libeio для асинхронного чтения. А если обработка будет разбита на отдельные кусочки с process.nextTick() между ними, то в эту обработку может вклиниться первичное получение HTTP-запроса и запрос на асинхронное чтение. За счёт этого суммарное время обслуживания первого запроса увеличится на небольшое время принятия второго HTTP-запроса, тогда как суммарное время обслуживания второго запроса уменьшится за счёт того, что асинхронное чтение начнётся раньше.

        Вы однозначно правы в том, что здесь не стоит делать категоричных заявлений о преимуществах того или иного кода. Уж слишком много факторов, влияющих на результат: размеры и диапазон размеров файлов, время обработки и его возможная нелинейная зависимость от размера файла, частота запросов и размера пула потоков в libeio. Я постараюсь в ближайшее время нарисовать «планы обработки» запросов для примеров из этого топика и различных вариантов «взаимного расположения» поступающих запросов, судя по моим наброскам они намного нагляднее и понятнее. Возможно мы сойдёмся на промежуточной точке зрения :)
    • НЛО прилетело и опубликовало эту надпись здесь
  • +1
    Графики неубедительные. Нужно было делать одну диаграмму — количество запросов в секунду от метода обработки. Но нагружать при тесте не одним файлом, а файлами с разным размером от 1/8 Кб до 1 Мб с каким то шагом, в случайном порядке с равномерным распределением частоты запросов от размера файла. Впрочем, эмпирически видится, что тип распределения тоже будет влиять на график. Поэтому для чистоты надо было бы еще и с нормальным распределением попробовать.
    • 0
      Согласен, нужно провести такой тест с множеством файлов. Для равномерного распределения применения не могу придумать, а случай с нормальным распределением вокруг какого-то значение подойдёт для моделирования сервиса потоковой обработки изображений.

      Также видится зависимость от времени обработки, видимо выберу последние два-три варианта кода из четырёх и буду строить диаграмму в зависимости от времени обработки.

      Мне, к сожалению, не хватает практического опыта для того, чтобы придумать качественный пример для тестирования :(
  • +1
    Пример аналогичного неблокирующего веб-серева с использованием fibers (библиотека node-sync):

    var Sync = require('sync');
    
    http.createServer(function (req, res) {
        
        // Very simple and dangerous check
        var filename = req.url.replace(/\?.*/, '').replace(/(\.\.|\/)/, '');
        
        // Start new fiber
        Sync(function(){
            
            // Read file from disk
            var filecontent = fs.readFile.sync(fs, filename, 'utf8');
            var str = func1_cb.sync(null, filecontent);
            var hash = func2_cb.sync(null, str);
            
            res.writeHead(200, {'Content-Type': 'text/plain'});
            res.end(hash);
            
        }, function(err){
    
            // Error handling
            if (err) {
                res.writeHead(404, {'Content-Type': 'text/plain'});
                res.end(err);
            }
        })
        
    }).listen(8124, "127.0.0.1");
    


    Было бы интересно сравнить производительность данного подхода.
    • 0
      Попробую. Как бы не забыть об этом :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.