原理
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