1# 利用native的方式实现跨线程调用 2 3<!--Kit: Common--> 4<!--Subsystem: Demo&Sample--> 5<!--Owner: @mgy917--> 6<!--Designer: @jiangwensai--> 7<!--Tester: @Lyuxin--> 8<!--Adviser: @huipeizi--> 9 10## 简介 11 12在OpenHarmony应用开发实践中,经常会遇到一些耗时的任务,如I/O操作、域名解析以及复杂计算等。这些任务如果直接在主线程中执行,将会严重阻塞主线程,影响后续任务的正常流程,进而导致用户界面响应延迟甚至卡顿。因此,为了提升代码性能,通常会将这类耗时任务放在子线程中执行。 13本文将聚焦于如何利用native的方式实现跨线程调用,即采用线程安全函数和libuv异步I/O工具库这两种策略,来优化程序性能并保持流畅的用户体验。 14 15## 注意事项 16 17以下将详细阐述如何运用native方式创建子线程以执行耗时任务,并确保与JavaScript的无缝交互。为此,开发者可以利用[arkui_napi](https://gitee.com/openharmony/arkui_napi)仓库提供的[NAPI(Node-API)](../reference/native-lib/napi.md)接口来实现跨语言调用的桥梁。该NAPI的设计严格遵循[Node.js](https://nodejs.org/api/n-api.html)的NAPI规范,以便开发者能够更轻松地理解和使用。 18特别强调的是,JavaScript函数通常只能在主线程里调用。如果native侧通过std::thread或pthread创建了子线程,那么napi_env、napi_value以及napi_ref是不能直接在子线程上下文中使用的。为确保正确性,当native端在子线程完成其计算或处理后,若需要回调JavaScript函数,必须先通过线程同步机制将结果传递回主线程,然后才能安全地在主线程环境中调用JavaScript函数。 19为解决这一问题,以下将提出两种有效的解决方案。 20 21## 解决方案 22 23### 线性安全函数 24 25napi_threadsafe_function 提供了接口来创建一个可以在多线程间共享并安全使用的函数对象。通过这个机制,子线程可以将数据传递给主线程,主线程接收到数据后会调用JavaScript回调函数进行处理。该接口包含用于创建、销毁线程安全函数以及在其之间发送消息和同步数据的方法。使用napi_threadsafe_function的一般步骤包括: 26 27**创建线程安全函数:** 通过调用napi_create_threadsafe_function()创建一个线程安全函数对象。在此过程中,需要指定一个JavaScript回调函数,该函数将在主线程上执行;同时设定相关的上下文信息,这个上下文可以在多个线程之间共享,可以随时通过调用napi_get_threadsafe_function_context()来获取。此外,还可以选择性地提供一个napi_finalize回调,用于在销毁线程安全函数时执行资源清理操作。 28 29**获取使用权:** 在开始使用线程安全函数之前,调用napi_acquire_threadsafe_function()函数表明线程已准备就绪,可以开始对该线程安全函数进行操作。 30 31**从子线程调用回调:** 在子线程中,通过调用napi_call_threadsafe_function()来异步触发JavaScript回调函数,并将所需数据作为参数传递给该回调函数。调用会被排队,并最终在JavaScript主线程上执行。 32 33**资源清理:** 当线程安全函数不再需要时,应当正确地释放和清理与其关联的资源。通常调用napi_release_threadsafe_function()函数来完成的,该函数会按照预定的策略处理尚未执行完毕的回调,并最终销毁线程安全函数对象。 34 35### 延长生命周期 36在JavaScript层面传递给native层的函数引用,其生命周期仅限于它所在的作用域内。若要确保在超出该作用域后仍能继续使用这个函数引用,需要采取适当的方法来延长其生命周期。 37可以通过调用napi_create_reference为JavaScript对象创建一个引用(reference)。这样可以避免对象因垃圾回收机制而被提前释放,从而有效地延长它的生命周期。然而,在创建引用之后,务必牢记要在不再需要该引用时,调用napi_delete_reference来释放引用,以防止内存泄漏问题的发生。 38深入理解并妥善管理JavaScript与native接口之间对象的生命周期,对于编写高效且无内存泄漏隐患的代码至关重要。建议开发者进一步研究[生命周期管理](./develop-Native-modules-using-NAPI-safely-and-efficiently.md)相关文档和最佳实践,以便更好地掌握。 39 40### libuv 41 42[libuv](../reference/native-lib/libuv.md)是一个基于事件驱动的异步I/O库,对于耗时操作,如果直接在libuv的主循环(event loop)中处理,会阻塞后续任务的执行。为解决这个问题,libuv内部维护了一个线程池,用于执行一些耗时操作,并在这些操作完成后,将回调函数添加回主线程的event loop中等待执行。 43默认情况下,libuv提供的线程池包含4个线程作为基本工作单元,但最大线程数可以扩展到128个。通过预先设置环境变量 UV_THREADPOOL_SIZE 的值,可以自定义线程池中的线程数量。当线程池初始化时,会创建相应数量的工作线程,并在每个线程内部运行一个 uv_queue_work 函数。 44值得注意的是,libuv 中的线程池是全局共享资源,不论应用中有多少个独立的事件循环实例,它们都共用同一个线程池。这样的设计旨在有效利用系统资源,同时避免因频繁创建和销毁线程带来的开销。 45 46**uv_queue_work** 47 48 ```c++ 49 uv_queue_work(uv_loop_t* loop, 50 uv_work_t* req, 51 uv_work_cb work_cb, 52 uv_after_work_cb after_work_cb); 53 ``` 54初始化一个工作请求,通过调用uv_queue_work函数,可以安排指定的任务,在与事件循环(event loop)关联的线程池中的一个线程上执行。一旦该任务(即work_cb回调函数)完成其操作,将在事件循环线程中调用另一个回调函数after_work_cb。 55各参数的具体意义如下: 56**loop:** 指向事件循环结构体的指针,所有异步操作都在这个事件循环上下文中进行管理。 57**req:** 指向uv_work_t结构体的指针,用于传递给工作请求和回调函数的数据。通常开发者会将自定义数据赋值给req->data成员变量以在回调中使用。 58**work_cb:** 执行实际工作的回调函数,一些耗时的操作可以在此执行,该函数在线程池的一个线程上运行。 59**after_work_cb:** 工作完成后在事件循环线程上调用的回调函数,常用于处理work_cb执行结果或触发进一步的JavaScript层面的操作。 60需要注意的是,尽管uv_queue_work方法本身不直接涉及NAPI(Node-API)接口,但当涉及到与JavaScript线程交互时,特别是从native层向JavaScript层传递数据并触发回调时,需要正确地管理napi_value对象的生命周期。这需要合理使用napi_handle_scope和相关接口,来确保在JavaScript回调方法创建的napi_value对象,在整个执行过程中保持有效,并在适当的时候释放资源,以避免内存泄漏问题。 61 62## 示例代码 63下面的示例分别用线程安全函数和libuv实现了native的跨线程调用。该示例在ArkTS端传入的JavaScript回调函数中对变量value进行加10运算,在native侧开启了3个子线程执行业务逻辑,子线程业务逻辑完成之后回到主线程执行ArkTS端传入的JavaScript回调函数,从而完成了对ArkTS端变量value的加30操作。[完整的示例代码](https://gitcode.com/openharmony/applications_app_samples/tree/master/code/Performance/PerformanceLibrary/feature/nativeThreadsCallJS)如下: 64### 1.使用线程安全函数 65**ArkTS实现一个JavaScript回调函数。** 66参数为param,函数体中对参数param加10后绑定变量value,并返回最新的param值。将回调函数作为参数调用native侧的ThreadSafeTest接口。 67 68 ```typescript 69 // src/main/ets/pages/Index.ets 70 71 Button("threadSafeTest") 72 .width('40%') 73 .fontSize(20) 74 .onClick(()=> { 75 // native使用线程安全函数实现跨线程调用 76 entry.ThreadSafeTest((param: number) => { 77 param += 10; 78 logger.info('ThreadSafeTest js callback value = ', param.toString()); 79 this.value = param; 80 return param; 81 } 82 ) 83 }).margin(20) 84 ``` 85**native主线程中实现一个ThreadSafeTest接口。** 86接口接收到ArkTS传入的JavaScript回调函数后通过napi_create_threadsafe_function创建一个线程安全函数tsfn,tsfn会回调主线程中的ThreadSafeCallJs,然后在ThreadSafeCallJs中调用ArkTS端传入的JavaScript回调函数。 87 88 ```c++ 89 // src/main/cpp/hello.cpp 90 91 napi_threadsafe_function tsfn; // 线程安全函数 92 static int g_cValue; // 保存value最新的值,作为参数传给js回调函数 93 int g_threadNum = 3; // 线程数 94 95 struct CallbackContext { 96 napi_env env = nullptr; 97 napi_ref callbackRef = nullptr; 98 int retData = 0; 99 }; 100 101 // 安全函数回调 102 static void ThreadSafeCallJs(napi_env env, napi_value js_cb, void *context, void *data) 103 { 104 CallbackContext *argContent = (CallbackContext *)data; 105 if (argContent != nullptr) { 106 OH_LOG_INFO(LOG_APP, "ThreadSafeTest CallJs start, retData:[%{public}d]", argContent->retData); 107 napi_get_reference_value(env, argContent->callbackRef, &js_cb); 108 } else { 109 OH_LOG_INFO(LOG_APP, "ThreadSafeTest CallJs argContent is null"); 110 return; 111 } 112 113 napi_valuetype valueType = napi_undefined; 114 napi_typeof(env, js_cb, &valueType); 115 if (valueType != napi_valuetype::napi_function) { 116 OH_LOG_ERROR(LOG_APP, "ThreadSafeTest callback param is not function"); 117 if (argContent != nullptr) { 118 napi_delete_reference(env, argContent->callbackRef); 119 delete argContent; 120 argContent = nullptr; 121 OH_LOG_INFO(LOG_APP, "ThreadSafeTest delete argContent"); 122 } 123 return; 124 } 125 // 将当前value值作为参数调用js函数 126 napi_value argv; 127 napi_create_int32(env, g_cValue, &argv); 128 napi_value result = nullptr; 129 napi_call_function(env, nullptr, js_cb, 1, &argv, &result); 130 // g_cValue保存调用js后的返回结果 131 napi_get_value_int32(env, result, &g_cValue); 132 OH_LOG_INFO(LOG_APP, "ThreadSafeTest CallJs end, [%{public}d]", g_cValue); 133 if (argContent != nullptr) { 134 napi_delete_reference(env, argContent->callbackRef); 135 delete argContent; 136 argContent = nullptr; 137 OH_LOG_INFO(LOG_APP, "ThreadSafeTest delete argContent end"); 138 } 139 } 140 141 // 使用安全函数跨线程调用js函数 142 static napi_value ThreadSafeTest(napi_env env, napi_callback_info info) 143 { 144 size_t argc = 1; 145 napi_value js_cb; 146 napi_value workName; 147 // 获取ArkTS 参数 148 napi_get_cb_info(env, info, &argc, &js_cb, nullptr, nullptr); 149 // 判断参数类型 150 napi_valuetype valueType = napi_undefined; 151 napi_typeof(env, js_cb, &valueType); 152 if (valueType != napi_valuetype::napi_function) { 153 OH_LOG_ERROR(LOG_APP, "ThreadSafeTest callback param is not function"); 154 return nullptr; 155 } 156 OH_LOG_INFO(LOG_APP, "ThreadSafeTest current value: [%{public}d]", g_cValue); 157 158 // 使用安全线程跨线程调用js 函数 159 napi_create_string_utf8(env, "workItem", NAPI_AUTO_LENGTH, &workName); 160 // 创建线程安全函数 161 napi_create_threadsafe_function(env, js_cb, NULL, workName, 0, 1, NULL, NULL, NULL, ThreadSafeCallJs, &tsfn); 162 } 163 ``` 164**在native子线程中调用线程安全函数。** 165通过std::thread创建子线程,在子线程中通过napi_call_threadsafe_function调用线程安全函数tsfn,把CallbackContext 结构体数据作为参数传入ThreadSafeCallJs。这里在子线程中进行了简单的业务处理,开发者可以根据自身实际需求进行相应的业务操作。 166 167 ```c++ 168// src/main/cpp/hello.cpp 169 170// 在子线程中调用线程安全函数 171 for (int i = 0; i < g_threadNum; i++) { 172 // 创建回调参数 173 auto asyncContext = new CallbackContext(); 174 asyncContext->env = env; 175 asyncContext->retData = i; 176 napi_create_reference(env, js_cb, 1, &asyncContext->callbackRef); 177 std::thread t([asyncContext]() { 178 // 处理业务逻辑 179 OH_LOG_INFO(LOG_APP, "ThreadSafeTest ChildTread start, index:[%{public}d], value: [%{public}d]", 180 asyncContext->retData, g_cValue); 181 asyncContext->retData++; 182 // 请求线程安全函数 183 napi_acquire_threadsafe_function(tsfn); 184 // 调用线程安全函数 185 napi_call_threadsafe_function(tsfn, asyncContext, napi_tsfn_nonblocking); 186 OH_LOG_INFO(LOG_APP, "ThreadSafeTest ChildTread end, index:[%{public}d], value: [%{public}d]", 187 asyncContext->retData, g_cValue); 188 189 /* 以下直接在子线程中调用js函数,会崩溃 190 napi_value result = nullptr; 191 napi_value argv; 192 napi_create_int32(env,g_cValue, &argv); 193 napi_call_function(env, nullptr, js_cb, 1, &argv, &result); 194 */ 195 }); 196 t.join(); 197 } 198 // 释放安全线程 199 napi_status status = napi_release_threadsafe_function(tsfn, napi_tsfn_release); 200 if (status == napi_status::napi_ok) { 201 OH_LOG_INFO(LOG_APP, "ThreadSafeTest napi_tsfn_release success."); 202 } else { 203 OH_LOG_INFO(LOG_APP, "ThreadSafeTest napi_tsfn_release fail !"); 204 } 205 ``` 206 207### 2.使用libuv 208**ArkTS实现一个JavaScript回调函数。** 209参数为param,函数体中对参数param加10后绑定变量value,并返回最新的param值。然后将回调函数作为参数调用native侧的UvWorkTest接口。 210 211 ```typescript 212 // src/main/ets/pages/Index.ets 213 214 Button("libuvTest") 215 .width('40%') 216 .fontSize(20) 217 .onClick(()=> { 218 // native使用线程安全函数实现跨线程调用 219 entry.UvWorkTest((param: number) => { 220 param += 10; 221 logger.info('UvWorkTest js callback value = ', param.toString()); 222 this.value = param; 223 return param; 224 } 225 ) 226 }).margin(20) 227 ``` 228**native主线程中实现一个UvWorkTest接口。** 229接口接收到ArkTS传入的JavaScript回调函数后创建子线程,在子线程的执行函数CallbackUvWorkTest中创建工作任务workReq,通过uv_queue_work将工作任务添加到libuv队列中。 230 231 ```c++ 232 // src/main/cpp/hello.cpp 233 234 void CallbackUvWorkTest(CallbackContext *context) 235 { 236 if (context == nullptr) { 237 OH_LOG_ERROR(LOG_APP, "UvWorkTest context is nullptr"); 238 return; 239 } 240 uv_loop_s *loop = nullptr; 241 napi_get_uv_event_loop(context->env, &loop); 242 // 创建工作数据结构,自定义数据结构添加在data中 243 uv_work_t *workReq = new uv_work_t; 244 if (workReq == nullptr) { 245 if (context != nullptr) { 246 napi_delete_reference(context->env, context->callbackRef); 247 delete context; 248 OH_LOG_INFO(LOG_APP, "UvWorkTest delete context"); 249 context = nullptr; 250 } 251 OH_LOG_ERROR(LOG_APP, "UvWorkTest new uv_work_t fail!"); 252 return; 253 } 254 workReq->data = (void *)context; 255 // 此打印位于子线程 256 OH_LOG_INFO(LOG_APP, "UvWorkTest childThread_1 [%{public}d]", g_cValue); 257 // 添加工作任务到libuv的队列中 258 uv_queue_work(loop, workReq, WorkCallback, AfterWorkCallback); 259 } 260 261 // 使用uv_work callback 实现跨线程调用js函数 262 static napi_value UvWorkTest(napi_env env, napi_callback_info info) 263 { 264 size_t argc = 1; 265 napi_value argv[1] = {0}; 266 napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); 267 268 napi_valuetype valueType = napi_undefined; 269 napi_typeof(env, argv[0], &valueType); 270 if (valueType != napi_function) { 271 OH_LOG_ERROR(LOG_APP, "UvWorkTest param is not function"); 272 return nullptr; 273 } 274 275 OH_LOG_INFO(LOG_APP, "UvWorkTest current value:[%{public}d]", g_cValue); 276 for (int i = 0; i < g_threadNum; i++) { 277 auto asyncContext = new CallbackContext(); 278 if (asyncContext == nullptr) { 279 OH_LOG_ERROR(LOG_APP, "UvWorkTest new asyncContext fail!"); 280 return nullptr; 281 } 282 asyncContext->env = env; 283 asyncContext->retData = i; 284 OH_LOG_INFO(LOG_APP, "UvWorkTest thread begin index:[%{public}d], value:[%{public}d]", i, g_cValue); 285 napi_create_reference(env, argv[0], 1, &asyncContext->callbackRef); 286 // using callback function on other thread 287 std::thread testThread(CallbackUvWorkTest, asyncContext); 288 testThread.detach(); 289 OH_LOG_INFO(LOG_APP, "UvWorkTest thread end index:[%{public}d], value:[%{public}d]", i, g_cValue); 290 } 291 return nullptr; 292 } 293 ``` 294**实现work_cb与after_work_cb。** 295work_cb位于子线程中,执行实际的业务逻辑;after_work_cb位于主线程中,通过napi_call_function调用ArkTS端传入的JavaScript回调函数。 296 297 ```c++ 298 // src/main/cpp/hello.cpp 299 300 void WorkCallback(uv_work_t *workReq) 301 { 302 // 另外一个子线程,一些耗时操作可以在此进行. 此处不能调用js函数. 303 CallbackContext *context = (CallbackContext *)workReq->data; 304 if (context != nullptr) { 305 OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack1 childThread_2 [%{public}d]", context->retData); 306 context->retData++; 307 OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack2 childThread_2 [%{public}d]", context->retData); 308 } else { 309 OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack3 childThread_2 context is null."); 310 } 311 } 312 313 void AfterWorkCallback(uv_work_t *workReq, int status) 314 { 315 CallbackContext *context = (CallbackContext *)workReq->data; 316 // 主线程执行,可以在此调用js函数 317 OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack mainThread [%{public}d]", context->retData); 318 napi_handle_scope scope = nullptr; 319 napi_open_handle_scope(context->env, &scope); 320 if (scope == nullptr) { 321 if (context != nullptr) { 322 napi_delete_reference(context->env, context->callbackRef); 323 delete context; 324 context = nullptr; 325 } 326 if (workReq != nullptr) { 327 delete workReq; 328 workReq = nullptr; 329 } 330 return; 331 } 332 napi_value callback = nullptr; 333 napi_get_reference_value(context->env, context->callbackRef, &callback); 334 napi_value retArg; 335 OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack begin [%{public}d]", g_cValue); 336 napi_create_int32(context->env, g_cValue, &retArg); 337 napi_value ret; 338 napi_call_function(context->env, nullptr, callback, 1, &retArg, &ret); 339 // 保存js回调结果 340 napi_get_value_int32(context->env, ret, &g_cValue); 341 OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack end [%{public}d]", g_cValue); 342 343 napi_close_handle_scope(context->env, scope); 344 if (context != nullptr) { 345 napi_delete_reference(context->env, context->callbackRef); 346 delete context; 347 OH_LOG_INFO(LOG_APP, "UvWorkTest delete context"); 348 context = nullptr; 349 } 350 if (workReq != nullptr) { 351 delete workReq; 352 OH_LOG_INFO(LOG_APP, "UvWorkTest delete work"); 353 workReq = nullptr; 354 } 355 } 356 ``` 357 358## 总结 359线程安全函数和libuv方案都是在子线程的执行函数运行结束后回到主线程,并将JavaScript回调函数push到主线程的event-loop队列里等待被执行。 360两者的差异在于libuv的子线程属于libuv线程池,而线程安全函数的子线程需要根据业务要求自己创建。另外在libuv中,JavaScript回调函数只能在子线程的主函数执行完毕后被动被执行;而在线程安全函数中,JavaScript回调函数则可以在任意线程中主动调用。 361