🌐 Thread-Safe Functions
JavaScript 函数通常只能从本地插件的主线程调用。如果一个插件创建了额外的线程,那么需要 Napi::Env、Napi::Value 或 Napi::Reference 的 node-addon-api 函数不能从这些线程调用。
🌐 JavaScript functions can normally only be called from a native addon's main thread. If an addon creates additional threads, then node-addon-api functions that require a Napi::Env, Napi::Value, or Napi::Reference must not be called from those threads.
当一个插件有额外的线程,并且需要根据这些线程完成的处理调用 JavaScript 函数时,这些线程必须与插件的主线程通信,以便主线程可以代表它们调用 JavaScript 函数。线程安全函数 API 提供了一种简单的方法来实现这一点。
🌐 When an addon has additional threads and JavaScript functions need to be invoked based on the processing completed by those threads, those threads must communicate with the addon's main thread so that the main thread can invoke the JavaScript function on their behalf. The thread-safe function APIs provide an easy way to do this.
一个线程安全的函数通过 ThreadSafeFunction::New 在主线程上创建:
🌐 A thread-safe function is created on the main thread via ThreadSafeFunction::New:
New(napi_env env,
const Function& callback,
const Object& resource,
ResourceString resourceName,
size_t maxQueueSize,
size_t initialThreadCount,
ContextType* context,
Finalizer finalizeCallback,
FinalizerDataType* data);一个线程安全的函数封装了:
🌐 A thread-safe function encapsulates:
- 消息队列:运行 JavaScript 函数的请求会被放入队列,由主线程异步处理。在返回
NonBlockingCall()的“队列已满”错误之前,队列中允许的条目数量由maxQueueSize参数控制(指定0可实现无限队列) - JavaScript 函数:要运行的回调(
callback参数)。该函数要么 (a) 在通过无参数的[Non]BlockingCall()重载调用时自动运行且不带参数,或者 (b) 作为参数传递给[Non]BlockingCall(DataType* data, Callback callback)重载中提供的回调函数。 - 上下文:可选的任意数据(
context参数),用于与线程安全函数关联。 - 终结器:可选回调(
finalizeCallback参数),在线程安全函数销毁时运行,当所有线程都已完成使用它。 - 终结器数据:提供给终结器回调的可选数据(
data参数)。
🌐 Calling the Thread-Safe Function
线程可以通过 [Non]BlockingCall 调用 JavaScript。这将向底层线程安全函数的队列中添加一个条目,以便在主线程处理事件循环时异步处理。
🌐 Threads may call into JavaScript via [Non]BlockingCall. This will add an entry to the underlying thread-safe function's queue, to be handled asynchronously on the main thread during its processing of the event loop.
🌐 Thread Management
多个线程可以同时使用线程安全函数。线程安全函数通过计算正在使用它的线程数量来管理其生命周期。这个数量从 New() 中的初始线程计数参数开始,通过 Acquire() 增加,通过 Release() 减少。一旦活动线程的数量达到零,线程安全函数将被销毁,如果提供了终结器回调,将在主线程上运行该回调。
🌐 Multiple threads can utilize the thread-safe function simultaneously. The thread-safe function manages its lifecycle through counting the number of threads actively utilizing it. This number starts at the initial thread count parameter in New(), increased via Acquire(), and decreased via Release(). Once the number of active threads reaches zero, the thread-safe function is destroyed, running the finalizer callback on the main thread if provided.
以下是在应用中使用线程安全函数的两种通用方法:
🌐 Here are two general approaches to using thread-safe functions within applications:
🌐 Known Number of Threads
如果在创建线程安全函数时已知线程数量,请在调用 New() 时将 initial_thread_count 参数设置为该数字。每个线程在调用 Release() 之前将拥有对线程安全函数的独立访问权限。一旦所有线程都对 Release() 进行了调用,线程安全函数将被销毁。
🌐 If the amount of threads is known at thread-safe function creation, set the initial_thread_count parameter to this number in the call to New(). Each thread will have its own access to the thread-safe function until it calls Release(). Once all threads have made a call to Release(), the thread-safe function is destroyed.
🌐 Creating Threads
另一个常见的用例是在运行时根据各种逻辑动态创建和销毁线程。处理这种情况的一种方法是公开几个本地 JavaScript 函数,这些函数通过以下方式与线程安全函数 API 交互:
🌐 Another common use-case is to dynamically create and destroy threads based on various logic at run-time. One way to handle this scenario is to expose several native JavaScript functions that interact with the thread-safe function APIs by:
- 通过
New()创建一个线程安全的函数,初始线程数为1。 - 调用
Acquire()并创建一个新的本地线程。新线程现在可以使用[Non]BlockingCall()。 - 启动清理/销毁,例如通过...
- 调用
Abort(),并让每个线程调用[Non]BlockingCall()或Release() - 使用自定义逻辑和其他线程安全的 API 来确保所有线程按顺序调用
Release(),以将活动线程数减少到0。
🌐 Example
此示例公开了一个创建线程安全函数和原生线程的单一函数。该函数返回一个在原生线程调用 JavaScript 十次后解决的 Promise。该示例由三个源文件组成:binding.gyp 配置构建,addon.cc 实现原生模块,addon.js 从 JavaScript 调用它。
🌐 This example exposes a single function that creates a thread-safe function and a native thread. The function returns a promise that resolves after the native thread calls into JavaScript ten times. The example consists of three source files: binding.gyp configures the build, addon.cc implements the native module, and addon.js exercises it from JavaScript.
运行 addon.js 会产生类似的输出:
🌐 Running addon.js produces output similar to:
2019-11-25T22:14:56.175Z 0
2019-11-25T22:14:56.380Z 1
2019-11-25T22:14:56.582Z 2
2019-11-25T22:14:56.787Z 3
2019-11-25T22:14:56.987Z 4
2019-11-25T22:14:57.187Z 5
2019-11-25T22:14:57.388Z 6
2019-11-25T22:14:57.591Z 7
2019-11-25T22:14:57.796Z 8
2019-11-25T22:14:58.001Z 9
true
🌐 Frequently Asked Questions
🌐 Q: My application isn't exiting correctly. It just hangs.
默认情况下,Node 会在线程安全函数完成之前等待,然后才进行清理和退出。请参见 线程管理。可以通过调用 Unref() 更改此行为,允许 Node 在不等待线程计数归零的情况下进行清理。调用 Ref() 会将线程安全函数恢复到先前的退出行为,要求所有使用它的线程必须 Release() 和/或 Abort()。
🌐 By default, Node will wait until a thread-safe function is finalized before cleaning up and exiting. See Thread Management. This behavior can be changed via a call to Unref(), permitting Node to clean up without waiting for the thread count to reach zero. A call to Ref() will return the threadsafe function to the previous exit behavior, requiring it to be Release()ed and/or Abort()ed by all threads utilizing it.
🌐 Q: If a thread receives napi_closing from a call to [Non]BlockingCall(), does it still need to call Release()?
不可以。返回值 napi_closing 应该向线程表明该线程安全函数不能再被使用。这包括对 Release() 的调用。
🌐 No. A return value of napi_closing should signify to the thread that the thread-safe function can no longer be utilized. This includes the call to Release().