使用Node-API接口进行线程安全开发
====================

#### 场景介绍

[napi\_create\_threadsafe\_function](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/napi#napi_create_threadsafe_function)是Node-API接口之一，用于创建一个线程安全的JavaScript函数。该函数主要用于在多个线程之间共享和调用，避免竞争条件和死锁。包含以下场景：

*   异步计算：若需执行耗时的计算或IO操作，可创建线程安全的函数，在另一线程中完成计算或IO操作，避免阻塞主线程，提升程序响应速度。
    
*   数据共享：若多个线程需访问同一份数据，可以创建一个线程安全的函数，避免数据进行读写操作时发生竞争条件或死锁等问题。
    
*   多线程编程：若需要进行多线程编程，可以创建一个线程安全的函数，确保多个线程之间的通信和同步操作正确。
    

#### 使用示例

1.  CMakeLists.txt配置
    
    \# the minimum version of CMake.
    cmake\_minimum\_required(VERSION 3.5.0)
    project(MyApplication3)
    
    set(NATIVERENDER\_ROOT\_PATH ${CMAKE\_CURRENT\_SOURCE\_DIR})
    
    if(DEFINED PACKAGE\_FIND\_FILE)
        include(${PACKAGE\_FIND\_FILE})
    endif()
    add\_definitions( "-DLOG\_DOMAIN=0xd0d0" )
    add\_definitions( "-DLOG\_TAG=\\"testTag\\"" )
    include\_directories(${NATIVERENDER\_ROOT\_PATH}
                        ${NATIVERENDER\_ROOT\_PATH}/include)
    
    add\_library(entry SHARED napi\_init.cpp)
    target\_link\_libraries(entry PUBLIC libace\_napi.z.so libhilog\_ndk.z.so)
    
    add\_library(entry1 SHARED thread\_safety.cpp)
    target\_link\_libraries(entry1 PUBLIC libace\_napi.z.so libhilog\_ndk.z.so)
    
2.  定义线程安全函数在Native入口。
    
    #include "napi/native\_api.h"
    #include "hilog/log.h"
    #include <future>
    
    static constexpr int INT\_ARG\_2 = 2; // 入参索引
    static constexpr int INT\_BUF\_32 = 32; // 入参索引
    
    struct CallbackData {
        napi\_threadsafe\_function tsfn;
        napi\_async\_work work;
    };
    
    // 在工作线程中调用ExecuteWork并执行线程安全函数
    static void ExecuteWork(napi\_env env, void \*data)
    {
        CallbackData \*callbackData = reinterpret\_cast<CallbackData \*>(data);
        std::promise<std::string> promise;
        auto future = promise.get\_future();
        napi\_acquire\_threadsafe\_function(callbackData->tsfn);
        napi\_call\_threadsafe\_function(callbackData->tsfn, &promise, napi\_tsfn\_nonblocking);
        napi\_release\_threadsafe\_function(callbackData->tsfn, napi\_tsfn\_release);
        try {
            auto result = future.get();
            OH\_LOG\_INFO(LOG\_APP, "XXX, Result from JS %{public}s", result.c\_str());
        } catch (const std::exception &e) {
            OH\_LOG\_INFO(LOG\_APP, "XXX, Result from JS %{public}s", e.what());
        }
    }
    
    static napi\_value ResolvedCallback(napi\_env env, napi\_callback\_info info)
    {
        void \*data = nullptr;
        size\_t argc = 1;
        napi\_value argv\[1\];
        if (napi\_get\_cb\_info(env, info, &argc, argv, nullptr, &data) != napi\_ok) {
            return nullptr;
        }
        size\_t result = 0;
        char buf\[32\] = {0};
        napi\_get\_value\_string\_utf8(env, argv\[0\], buf, INT\_BUF\_32, &result);
        reinterpret\_cast<std::promise<std::string> \*>(data)->set\_value(std::string(buf));
        return nullptr;
    }
    
    static napi\_value RejectedCallback(napi\_env env, napi\_callback\_info info)
    {
        void \*data = nullptr;
        if (napi\_get\_cb\_info(env, info, nullptr, nullptr, nullptr, &data) != napi\_ok) {
            return nullptr;
        }
        reinterpret\_cast<std::promise<std::string> \*>(data)->set\_exception(
            std::make\_exception\_ptr(std::runtime\_error("Error in jsCallback")));
        return nullptr;
    }
    
    static void CallJs(napi\_env env, napi\_value jsCb, void \*context, void \*data)
    {
        if (env == nullptr) {
            return;
        }
        napi\_value undefined = nullptr;
        napi\_value promise = nullptr;
        napi\_get\_undefined(env, &undefined);
        napi\_call\_function(env, undefined, jsCb, 0, nullptr, &promise);
        napi\_value thenFunc = nullptr;
        if (napi\_get\_named\_property(env, promise, "then", &thenFunc) != napi\_ok) {
            return;
        }
        napi\_value resolvedCallback;
        napi\_value rejectedCallback;
        napi\_create\_function(env, "resolvedCallback", NAPI\_AUTO\_LENGTH, ResolvedCallback, data, &resolvedCallback);
        napi\_create\_function(env, "rejectedCallback", NAPI\_AUTO\_LENGTH, RejectedCallback, data, &rejectedCallback);
        napi\_value argv\[2\] = {resolvedCallback, rejectedCallback};
        napi\_call\_function(env, promise, thenFunc, INT\_ARG\_2, argv, nullptr);
    }
    
    // 任务执行完成后，进行资源清理回收
    static void WorkComplete(napi\_env env, napi\_status status, void \*data)
    {
        CallbackData \*callbackData = reinterpret\_cast<CallbackData \*>(data);
        napi\_release\_threadsafe\_function(callbackData->tsfn, napi\_tsfn\_release);
        napi\_delete\_async\_work(env, callbackData->work);
        callbackData->tsfn = nullptr;
        callbackData->work = nullptr;
        delete callbackData;
    }
    
    static napi\_value StartThread(napi\_env env, napi\_callback\_info info)
    {
        CallbackData \*callbackData = new CallbackData();
        size\_t argc = 1;
        napi\_value jsCb = nullptr;
        napi\_get\_cb\_info(env, info, &argc, &jsCb, nullptr, nullptr);
    
        // 创建一个线程安全函数
        napi\_value resourceName = nullptr;
        napi\_create\_string\_utf8(env, "Thread-safe Function Demo", NAPI\_AUTO\_LENGTH, &resourceName);
        napi\_create\_threadsafe\_function(env, jsCb, nullptr, resourceName, 0, 1, callbackData, nullptr, callbackData, CallJs,
                                        &callbackData->tsfn);
    
        // 创建一个异步任务
        // ExecuteWork会执行在一个由libuv创建的非JS线程上
        // 此处使用napi\_create\_async\_work是为了模拟在非JS线程场景使用napi\_call\_threadsafe\_function接口向JS线程提交任务
        napi\_create\_async\_work(env, nullptr, resourceName, ExecuteWork, WorkComplete, callbackData, &callbackData->work);
    
        // 将异步任务加入到异步队列中
        napi\_queue\_async\_work(env, callbackData->work);
        return nullptr;
    }
    
3.  模块注册。
    
    EXTERN\_C\_START
    static napi\_value Init(napi\_env env, napi\_value exports)
    {
        napi\_property\_descriptor desc\[\] = {
            {"startThread", nullptr, StartThread, nullptr, nullptr, nullptr, napi\_default, nullptr}
        };
        napi\_define\_properties(env, exports, sizeof(desc) / sizeof(desc\[0\]), desc);
        return exports;
    }
    EXTERN\_C\_END
    
    static napi\_module demoModule = {
        .nm\_version = 1,
        .nm\_flags = 0,
        .nm\_filename = nullptr,
        .nm\_register\_func = Init,
        .nm\_modname = "entry1",
        .nm\_priv = ((void \*)0),
        .reserved = {0},
    };
    
    extern "C" \_\_attribute\_\_((constructor)) void RegisterEntryModule(void) { napi\_module\_register(&demoModule); }
    
4.  ArkTS侧示例代码
    
    export const startThread: (a: () => Promise<string>) => void;
    
    导入头文件
    
    import nativeModule from 'libentry1.so';
    
    // index.ets
    let callback = (): Promise<string> => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve('string from promise');
        }, 5000);
      });
    }
    nativeModule.startThread(callback);
    

#### 子线程交互场景介绍

*   napi\_threadsafe\_function在主线程和子线程使用并无差异，下面是子线程的使用示例。

#### \[h2\]基于[Worker](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/worker-introduction)实现的C++子线程与ArkTS子线程交互场景

1.  CMakeLists.txt配置
    
    \# the minimum version of CMake.
    cmake\_minimum\_required(VERSION 3.5.0)
    project(MyApplication3)
    
    set(NATIVERENDER\_ROOT\_PATH ${CMAKE\_CURRENT\_SOURCE\_DIR})
    
    if(DEFINED PACKAGE\_FIND\_FILE)
        include(${PACKAGE\_FIND\_FILE})
    endif()
    add\_definitions( "-DLOG\_DOMAIN=0xd0d0" )
    add\_definitions( "-DLOG\_TAG=\\"testTag\\"" )
    include\_directories(${NATIVERENDER\_ROOT\_PATH}
                        ${NATIVERENDER\_ROOT\_PATH}/include)
    
    add\_library(entry SHARED napi\_init.cpp)
    target\_link\_libraries(entry PUBLIC libace\_napi.z.so libhilog\_ndk.z.so)
    
    add\_library(entry1 SHARED thread\_safety.cpp)
    target\_link\_libraries(entry1 PUBLIC libace\_napi.z.so libhilog\_ndk.z.so)
    
2.  在Native入口定义线程安全函数并创建子线程。
    
    #include "napi/native\_api.h"
    #include "hilog/log.h"
    #include <future>
    // ...
    
    struct TsfnContext {
        napi\_ref callbackRef;
    };
    
    struct ThreadData {
        std::string inputStr;
        napi\_threadsafe\_function tsfn;
    };
    
    // C++子线程
    void NativeThread(void\* arg)
    {
        auto\* data = static\_cast<ThreadData\*>(arg);
        OH\_LOG\_INFO(LOG\_APP, "\[C++ SubThread\] Received from Worker: %{public}s\\n", data->inputStr.c\_str());
        std::string str = "Hello from C++!";
        std::string msg = "Echo of " + str;
        char\* cstr = strdup(msg.c\_str());
        napi\_call\_threadsafe\_function(data->tsfn, cstr, napi\_tsfn\_nonblocking);
        napi\_release\_threadsafe\_function(data->tsfn, napi\_tsfn\_release);
        delete data;
    }
    
    // 在 JS 线程中实际执行的回调
    void CallJsCallback(napi\_env env, napi\_value jsCallback, void\* context, void\* data)
    {
        if (data == nullptr) {
            return;
        }
        char\* message = static\_cast<char\*>(data);
        napi\_value jsStr;
        napi\_create\_string\_utf8(env, message, NAPI\_AUTO\_LENGTH, &jsStr);
        napi\_value global;
        napi\_get\_global(env, &global);
        napi\_value result;
        napi\_call\_function(env, global, jsCallback, 1, &jsStr, &result);
        free(message);
    }
    
    // tsfn销毁时的清理回调
    void TsfnFinalizeCallback(napi\_env env, void\* finalizeData, void\* finalizeHint)
    {
        TsfnContext\* ctx = static\_cast<TsfnContext\*>(finalizeData);
        if (ctx && ctx->callbackRef) {
            napi\_delete\_reference(env, ctx->callbackRef);
            delete ctx;
        }
    }
    
    // ArkTS 调用的入口函数
    napi\_value StartWithCallback(napi\_env env, napi\_callback\_info info)
    {
        size\_t argc = 2;
        napi\_value args\[2\];
        napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
        size\_t length = 0;
        napi\_status status = napi\_get\_value\_string\_utf8(env, args\[0\], nullptr, 0, &length);
        if (status != napi\_ok) {
            OH\_LOG\_ERROR(LOG\_APP, "napi\_get\_value\_string\_utf8 failed");
            return nullptr;
        }
        char\* inputStr = new char\[length + 1\];
        std::fill(inputStr, inputStr + (length + 1), 0);
        status = napi\_get\_value\_string\_utf8(env, args\[0\], inputStr, length + 1, &length);
        if (status != napi\_ok) {
            if (inputStr) {
                delete\[\] inputStr;
            }
            OH\_LOG\_ERROR(LOG\_APP, "napi\_get\_value\_string\_utf8 failed");
            return nullptr;
        }
        std::string inputString(inputStr, length);
        delete\[\] inputStr;
        TsfnContext\* ctx = new TsfnContext();
        napi\_create\_reference(env, args\[1\], 1, &ctx->callbackRef);
        napi\_value resourceName;
        napi\_create\_string\_utf8(env, "TSFN\_WorkerToCpp", NAPI\_AUTO\_LENGTH, &resourceName);
        napi\_threadsafe\_function tsfn;
        napi\_create\_threadsafe\_function(env, args\[1\], nullptr, resourceName,
                                        0, 1, ctx, TsfnFinalizeCallback, nullptr, CallJsCallback, &tsfn);
        auto\* threadData = new ThreadData{std::move(inputString), tsfn};
        std::thread nativethread(NativeThread, threadData);
        nativethread.detach();
        return nullptr;
    }
    
3.  模块注册。
    
    EXTERN\_C\_START
    static napi\_value Init(napi\_env env, napi\_value exports)
    {
        napi\_property\_descriptor desc\[\] = {
            {"startWithCallback", nullptr, StartWithCallback, nullptr, nullptr, nullptr, napi\_default, nullptr}
        };
        napi\_define\_properties(env, exports, sizeof(desc) / sizeof(desc\[0\]), desc);
        return exports;
    }
    EXTERN\_C\_END
    
    static napi\_module demoModule = {
        .nm\_version = 1,
        .nm\_flags = 0,
        .nm\_filename = nullptr,
        .nm\_register\_func = Init,
        .nm\_modname = "entry1",
        .nm\_priv = ((void \*)0),
        .reserved = {0},
    };
    
    extern "C" \_\_attribute\_\_((constructor)) void RegisterEntryModule(void) { napi\_module\_register(&demoModule); }
    
4.  DevEco Studio支持一键生成Worker，在对应的{moduleName}目录下任意位置，点击鼠标右键 > New > Worker，即可自动生成Worker的模板文件及配置信息。本文以创建 "Worker" 为例。
    
    "buildOption": {
      "sourceOption": {
        "workers": \[
          "./src/main/ets/workers/Worker.ets"
         \]
      },
    }
    
5.  Worker线程示例代码。
    
    // entry/src/main/ets/workers/Worker.ets
    
    import nativeModule from 'libentry1.so';
    import { worker, MessageEvents } from '@kit.ArkTS';
    
    const port = worker.workerPort;
    
    port.onmessage = (e: MessageEvents) => {
      console.info('Worker thread received:' + e.data);
      nativeModule.startWithCallback('Hello', (result: string) => {
        console.info('\[Worker\] Got from native:', result);
        port.postMessage(result);
      });
    }
    
6.  接口对应的.d.ts描述。
    
    export const startWithCallback: (input: string, callback: (msg: string) => void) => void;
    
7.  ArkTS侧调用接口。
    
    import nativeModule from 'libentry1.so';
    import { worker } from '@kit.ArkTS';
    
    // index.ets
    const wk = new worker.ThreadWorker('entry/ets/workers/Worker.ets');
    wk.postMessage('Start');
    wk.onmessage = (msg) => {
      console.info('\[Main\] Received:', msg.data);
      wk.terminate();
    }
    
    运行结果：
    Worker thread received:Start
    \[C++ SubThread\] Received from Worker: Hello
    \[Worker\] Got from native: Echo of Hello from C++!
    \[Main\] Received: Echo of Hello from C++
    

#### \[h2\]基于[Taskpool](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/taskpool-introduction)实现的C++子线程与ArkTS子线程交互场景

1.  native侧实现代码以及模块注册与“基于Worker实现的C++子线程与ArkTS子线程交互场景”一致，可直接复用。
    
2.  ArkTS侧示例代码。
    
    import nativeModule from 'libentry1.so';
    import { taskpool } from '@kit.ArkTS';
    
    @Concurrent
    function nativeCall(input : string): void {
      console.info('Taskpool thread received:%s', input);
      nativeModule.startWithCallback('Hello', (result: string) => {
        console.info('\[Taskpool\] Got from native:', result);
      });
    }
    
    async function testTaskpool() : Promise<void> {
      try {
        const task = new taskpool.Task(nativeCall, 'Start');
        await taskpool.execute(task);
      } catch (e) {
        console.error(\`Taskpool execute error: ${e}\`);
      }
    }
    
    // index.ets
    testTaskpool();
    
    运行结果：
    Taskpool thread received:Start
    \[C++ SubThread\] Received from Worker: Hello
    \[Taskpool\] Got from native: Echo of Hello from C++!