napi_create_async_work 和 napi_create_threadsafe_function 的使用场景分析

原理

napi_create_async_work:Node.js 不适合处理耗时的操作,而 napi_create_async_work 适合利用 C/C++ 层提供的多线程来处理耗时操作
napi_create_async_work 可以通过 N-API 使用 Libuv 线程池

napi_create_async_work // 创建一个 work,但是还没有执行
napi_delete_async_work // 释放上面创建的 work 的内存
napi_queue_async_work  // 往 Libuv 提交一个 work
napi_cancel_async_work // 取消 Libuv 中的任务,如果已经在执行则无法取消

napi_create_threadsafe_function:在多线程环境下进行一些数据交互,需要格外注意线程安全, napi_create_threadsafe_function 函数可以创建一个线程安全函数,然后在任意线程中调用
应用场景:当 native 侧有其他线程,并且需要根据这些线程的完成结果调用 JavaScript 函数时,这些线程必须与 native 侧的主线程进行通信,才能在主线程中调用 JavaScript 函数。
线程安全函数便提供了一种简化方法,避免了线程间通讯,同时可以回到主线程调用 JavaScript 函数
tsfn(线程安全函数) 内部维护一个线程安全的队列,这样能确保数据按顺序传递,防止数据竞争和资源冲突

使用

napi_create_threadsafe_function 与 napi_create_async_work 一般要结合使用,在 napi_create_threadsafe_function 创建一个线程安全函数时,再使用 napi_create_async_work 创建一个 work 来处理异步的耗时操作
补充:需要实时数据处理的并不总是需要 napi_create_async_work,而是用单一线程 std::thread 处理,所以说如果是长时间占用的任务,用 async_work,有限时间的任务用 std::thread

napi_value CronetExecutor::Start(napi_env env, napi_callback_info info) {
  napi_status status;

  napi_value jsthis;
  if (!CronetUtil::ValidateAndGetCbInfo(env, info, &jsthis)) {
    return nullptr;
  }

  CronetExecutor* obj;
  DCHECK(napi_unwrap(env, jsthis, reinterpret_cast(&obj)));

  if (obj->started_) {
    napi_throw_error(env, nullptr, "CronetExecutor already started");
    return nullptr;
  }

  napi_value work_name;
  // Create a string to describe this asynchronous operation.
  DCHECK(napi_create_string_utf8(env,
                                 "CronetExecutor AsyncWork",
                                 NAPI_AUTO_LENGTH,
                                 &work_name));

  // Convert the callback retrieved from JavaScript into a thread-safe function
  // which we can call from a worker thread.
  DCHECK(napi_create_threadsafe_function(env,
                                         NULL,
                                         NULL,
                                         work_name,
                                         0,
                                         1,
                                         NULL,
                                         NULL,
                                         obj,
                                         CronetExecutor::CallJs,
                                         &(obj->tsfn_)));

  // Create an async work item, passing in the addon data, which will give the
  // worker thread access to the above-created thread-safe function.
  DCHECK(napi_create_async_work(env,
                                NULL,
                                work_name,
                                CronetExecutor::ExecuteWork,
                                CronetExecutor::WorkComplete,
                                obj,
                                &(obj->work_)));

  // Queue the work item for execution.
  DCHECK(napi_queue_async_work(env, obj->work_));
  obj->AddRef();
  obj->started_ = true;
  return nullptr;
}

// This function runs on a worker thread. It has no access to the JavaScript
// environment except through the thread-safe function.
void CronetExecutor::ExecuteWork(napi_env env, void* data) {
  CronetExecutor* executor = (CronetExecutor*)data;
  napi_status status;

  // We bracket the use of the thread-safe function by this thread by a call to
  // napi_acquire_threadsafe_function() here, and by a call to
  // napi_release_threadsafe_function() immediately prior to thread exit.
  DCHECK(napi_acquire_threadsafe_function(executor->tsfn_));

  executor->RunTasksInQueue();

  // Indicate that this thread will make no further use of the thread-safe function.
  DCHECK(napi_release_threadsafe_function(executor->tsfn_,
                                          napi_tsfn_release));
}

在 aysnc work 执行前,需要先 napi_acquire_threadsafe_function 获取线程安全函数,以确保线程之间的同步和互斥,再执行 work,完事后 release 线程安全函数
注意:napi_acquire_threadsafe_function 一般用于多线程之间的切换,如果 native 是单线程操作,无需其他线程,可以不使用 napi_acquire_threadsafe_function

tsfn 引用计数

借用这块的示例代码:https://github.com/nodejs/node-addon-api/blob/main/doc/threadsafe_function.md#example

// Create a ThreadSafeFunction
  tsfn = ThreadSafeFunction::New(
      env,
      info[0].As(),  // JavaScript function called asynchronously
      "Resource Name",         // Name
      0,                       // Unlimited queue
      1,                       // Only one thread will use this initially
      []( Napi::Env ) {        // Finalizer used to clean threads up
        nativeThread.join();
      } );

  // Create a native thread
  nativeThread = std::thread( [count] {
    auto callback = []( Napi::Env env, Function jsCallback, int* value ) {
      // Transform native data into JS data, passing it to the provided
      // `jsCallback` -- the TSFN's JavaScript function.
      jsCallback.Call( {Number::New( env, *value )} );

      // We're finished with the data.
      delete value;
    };

    for ( int i = 0; i < count; i++ )
    {
      // Create new data
      int* value = new int( clock() );

      // Perform a blocking call
      napi_status status = tsfn.BlockingCall( value, callback );
      if ( status != napi_ok )
      {
        // Handle error
        break;
      }

      std::this_thread::sleep_for( std::chrono::seconds( 1 ) );
    }

    // Release the thread-safe function
    tsfn.Release();
  } );

在创建 ThreadSafeFunction 时会根据 init 的线程数量来决定引用计数加几
比如说,示例中只 init 了一个线程,那么最后 tsfn 只需要 release 一次就行;后续 acquire 一次就需要 release 一次

DCHECK(napi_acquire_threadsafe_function(tsfn_));

  RunTasksInQueue();

  // Indicate that this thread will make no further use of the thread-safe function.
  DCHECK(napi_release_threadsafe_function(tsfn_, napi_tsfn_release));

当 tsfn 计数为 0 时,会销毁线程安全函数所用的资源

参考:

  • https://zhuanlan.zhihu.com/p/694166196
  • https://zhuanlan.zhihu.com/p/378064331
  • https://github.com/nodejs/node-addon-api/blob/main/doc/threadsafe_function.md

你可能感兴趣的:(java,开发语言)