Если nodejs использует неблокирующий IO, как реализуется fs.readFileSync?

Я вижу много синхронных функций в библиотеке файловой системы . например fs.readFileSync(filename, [options]).

Как (и почему) реализованы эти функции, если node имеет асинхронный/неблокирующий IO и метод сна - и могу ли я использовать тот же механизм для реализации других синхронных функций?

Ответ 1

fs.readFileSync()

на самом деле просто обертка для

fs.readSync() 

функция. Итак, вопрос в том, как реализуется fs.readSync() по сравнению с fs.read(). Если вы посмотрите на реализацию этих двух функций, они оба используют модуль привязок. Которая в этом случае инициализируется

var binding = process.binding('fs').  

а вызовы

binding.read(fd, buffer, offset, length, position, wrapper);//async
var r = binding.read(fd, buffer, offset, length, position);//sync

соответственно. Когда мы находимся в модуле "привязки", мы выходим в v8, узел _ #####. Cc land. Реализация привязки ('fs') может быть найдена в коде репозитория node, в node_file.cc. Механизм node предлагает перегрузки для вызовов С++, один из которых выполняет обратный вызов, а другой - нет. Код node_file.cc использует класс req_wrap. Это оболочка для двигателя v8. В node_file.cc мы видим следующее:

#define ASYNC_CALL(func, callback, ...)                           \
  FSReqWrap* req_wrap = new FSReqWrap(#func);                     \
  int r = uv_fs_##func(uv_default_loop(), &req_wrap->req_,        \
      __VA_ARGS__, After);                                        \
  req_wrap->object_->Set(oncomplete_sym, callback);               \
  req_wrap->Dispatched();                                         \
  if (r < 0) {                                                    \
    uv_fs_t* req = &req_wrap->req_;                               \
    req->result = r;                                              \
    req->path = NULL;                                             \
    req->errorno = uv_last_error(uv_default_loop()).code;         \
    After(req);                                                   \
  }                                                               \
  return scope.Close(req_wrap->object_);

#define SYNC_CALL(func, path, ...)                                \
  fs_req_wrap req_wrap;                                           \
  int result = uv_fs_##func(uv_default_loop(), &req_wrap.req, __VA_ARGS__, NULL); \
  if (result < 0) {                                               \
    int code = uv_last_error(uv_default_loop()).code;             \
    return ThrowException(UVException(code, #func, "", path));    \
  }

Обратите внимание, что SYNC_CALL использует другой req-wrap. Вот код для соответствующего конструктора req_wrap для метода ASYNC, найденного в req_wrap.h

ReqWrap() {
    v8::HandleScope scope;
    object_ = v8::Persistent<v8::Object>::New(v8::Object::New());

    v8::Local<v8::Value> domain = v8::Context::GetCurrent()
                                  ->Global()
                                  ->Get(process_symbol)
                                  ->ToObject()
                                  ->Get(domain_symbol);

    if (!domain->IsUndefined()) {
      // fprintf(stderr, "setting domain on ReqWrap\n");
      object_->Set(domain_symbol, domain);
    }

    ngx_queue_insert_tail(&req_wrap_queue, &req_wrap_queue_);
  }

Обратите внимание, что эта функция создает новый объект области v8 для управления запуском этого события. Здесь происходит асинхронная часть асинхронного материала. Двигатель v8 запускает новую среду интерпретации javascript для обработки этого конкретного вызова отдельно. Короче говоря, без создания/изменения собственной версии node вы не сможете реализовать свои собственные асинхронные/синхронные версии вызовов, так же, как это делает node. При этом асинхронный режим применим только к операциям ввода-вывода. Возможно, описание того, почему вы думаете, что вам нужно, чтобы быть более синхронным, было бы в порядке. В общем, если вы считаете, что node не поддерживает что-то, что вы хотите сделать, вы просто не охватываете механизм обратных вызовов для его полного потенциала.

При этом вы можете использовать модуль событий node для реализации собственных обработчиков событий, если вам нужно поведение async. И вы можете рассмотреть родные расширения, если есть вещи, которые вам отчаянно нужно делать синхронно, однако я настоятельно рекомендую против этого. Подумайте, как вы можете работать в цикле асинхронных событий, чтобы получить то, что вам нужно сделать таким образом. Обнимите этот стиль мышления или переключитесь на другой язык.

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