1# libuv 2 3## 简介 4 5[libuv](http://libuv.org/)是一个跨平台库,基于事件驱动来实现异步I/O,适用于网络编程和文件系统操作。它是Node.js的核心库之一,也被其他语言的开发者广泛使用。 6 7## 支持的能力 8 9[libuv](http://libuv.org/)实现了跨平台的基于事件驱动的异步I/O。 10 11支持标准库接口。 12 13## 引入libuv能力 14 15如果开发者需要使用libuv相关功能,首先请添加头文件: 16 17```c 18#include <uv.h> 19``` 20 21其次在CMakeLists.txt中添加以下动态链接库: 22 23``` 24libuv.so 25``` 26 27## 接口列表 28 29详见[libuv支持的API文档](http://docs.libuv.org/en/v1.x/api.html)。 30 31## OpenHarmony引入libuv的背景 32 33在OpenHarmony的早期版本中,为了兼容Node.js的生态,将Node.js的Node-API引入到系统中,方便Node.js开发者快速接入OpenHarmony,扩展自己的JS接口。同时引入了Node.js的事件循环实现库——libuv。 34 35### 演进方向 36 37随着OpenHarmony的逐步完善,我们计划在未来的版本中,逐步将应用模型中的事件循环归一,并增强OpenHarmony自身的事件循环,以解决许多双loop机制下的调度问题,并为开发者提供更加完善的任务优先级、插队等与任务主循环交互的方法。 38 39开发者应尽可能避免在`napi_get_uv_event_loop`接口获取的应用主loop上使用libuv的ndk进行操作,因为这可能会带来各种问题,并给未来的兼容性变更带来大量的工作量。 40 41如果开发者希望跟主线程事件循环交互,比如插入任务等,应当使用[Node-API提供的接口](../../napi/napi-data-types-interfaces.md)。 42 43OpenHarmony还将长期通过Node-API来为开发者提供和主线程交互及扩展JS接口的能力,但会屏蔽实现层使用的事件循环。Node-API的主要功能接口将会长期维护,并保证与Node.js的原生行为一致,来保证熟悉Node.js的扩展机制的开发者方便地将自己的已有代码接入到OpenHarmony中来。 44 45如果开发者对libuv非常熟悉,并自信能够处理好所有的内存管理和多线程问题,那么仍可以像使用原生libuv一样,自己启动线程,并在上面使用libuv完成自己的业务。在没有特殊版本要求的情况下,开发者不需要额外引入libuv库到自己的应用工程中。 46 47## 当前问题和解决方案 48 49根据现有机制,一个线程上只能存在一个事件循环,为了适配系统应用的主事件循环,在主线程上的JS环境中,uvloop中的事件处理是由主事件循环监听其fd,触发一次`uv_run`来驱动的。因此部分依赖uvloop事件循环的功能无法生效。 50 51基于上述,比较常用的场景和解决方案有: 52 53### 场景一、在JS主线程抛异步任务到工作线程执行,在主线程中执行JS代码处理返回结果 54 55**错误示例:** 56 57在Native侧直接通过调用`napi_get_uv_event_loop`接口获取系统loop,调用libuv NDK接口实现相关功能。 58 59ArkTS侧: 60```typescript 61import { hilog } from '@kit.PerformanceAnalysisKit'; 62import testNapi from 'libentry.so' 63 64@Entry 65@Component 66struct Index { 67 build() { 68 Row() { 69 Column() { 70 Button("test") 71 .width('40%') 72 .fontSize('14fp') 73 .onClick(() => { 74 testNapi.test(); 75 }).margin(20) 76 }.width('100%') 77 }.height('100%') 78 } 79} 80``` 81Native侧: 82```cpp 83#include "napi/native_api.h" 84#include "uv.h" 85#define LOG_DOMAIN 0X0202 86#define LOG_TAG "MyTag" 87#include <hilog/log.h> 88 89static void execute(uv_work_t* work) 90{ 91 OH_LOG_INFO(LOG_APP, "ohos in execute"); 92} 93 94static void complete(uv_work_t* work, int status) 95{ 96 OH_LOG_INFO(LOG_APP, "ohos in complete"); 97 delete work; 98} 99static napi_value Test(napi_env env, napi_callback_info info) 100{ 101 uv_loop_s* loop = nullptr; 102 /* 获取应用JS主线程的uv_loop */ 103 napi_get_uv_event_loop(env, &loop); 104 uv_work_t* work = new uv_work_t; 105 int ret = uv_queue_work(loop, work, execute, complete); 106 if (ret != 0) { 107 OH_LOG_INFO(LOG_APP, "delete work"); 108 delete work; 109 } 110 return 0; 111} 112 113EXTERN_C_START 114static napi_value Init(napi_env env, napi_value exports) 115{ 116 napi_property_descriptor desc[] = {{"test", nullptr, Test, nullptr, nullptr, nullptr, napi_default, nullptr}}; 117 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 118 return exports; 119} 120EXTERN_C_END 121 122static napi_module demoModule = { 123 .nm_version = 1, 124 .nm_flags = 0, 125 .nm_filename = nullptr, 126 .nm_register_func = Init, 127 .nm_modname = "entry", 128 .nm_priv = ((void *)0), 129 .reserved = {0}, 130}; 131 132extern "C" __attribute__((constructor)) void RegisterEntryModule(void) 133{ 134 napi_module_register(&demoModule); 135} 136``` 137 138在index.d.ts文件中怎添加如下代码: 139``` 140export const test:() => number; 141``` 142 143**正确示例:** 144 145可通过`napi_create_async_work`、`napi_queue_async_work`搭配使用。 146 147ArkTS侧: 148```typescript 149import { hilog } from '@kit.PerformanceAnalysisKit'; 150import testNapi from 'libentry.so' 151 152@Entry 153@Component 154struct Index { 155 build() { 156 Row() { 157 Column() { 158 Button("test") 159 .width('40%') 160 .fontSize('14fp') 161 .onClick(() => { 162 testNapi.test(); 163 }).margin(20) 164 }.width('100%') 165 }.height('100%') 166 } 167} 168``` 169Native侧: 170```cpp 171#include "napi/native_api.h" 172#include "uv.h" 173#define LOG_DOMAIN 0X0202 174#define LOG_TAG "MyTag" 175#include <hilog/log.h> 176uv_loop_t* loop = nullptr; 177napi_value jsCb; 178int fd = -1; 179 180static napi_value Test(napi_env env, napi_callback_info info) 181{ 182 napi_value work_name; 183 napi_async_work work; 184 napi_create_string_utf8(env, "ohos", NAPI_AUTO_LENGTH, &work_name); 185 /* 第四个参数是异步线程的work任务,第五个参数为主线程的回调 */ 186 napi_create_async_work( 187 env, nullptr, work_name, [](napi_env env, void* data){OH_LOG_INFO(LOG_APP, "ohos in execute"); }, 188 [](napi_env env, napi_status status, void* data){ 189 /* 不关心具体实现 */ 190 OH_LOG_INFO(LOG_APP, "ohos in complete"); 191 napi_delete_async_work(env, (napi_async_work)data); 192 }, 193 nullptr, &work); 194 /* 通过napi_queue_async_work触发异步任务执行 */ 195 napi_queue_async_work(env, work); 196 return 0; 197} 198 199EXTERN_C_START 200static napi_value Init(napi_env env, napi_value exports) 201{ 202 napi_property_descriptor desc[] = {{"test", nullptr, Test, nullptr, nullptr, nullptr, napi_default, nullptr}}; 203 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 204 return exports; 205} 206EXTERN_C_END 207 208static napi_module demoModule = { 209 .nm_version = 1, 210 .nm_flags = 0, 211 .nm_filename = nullptr, 212 .nm_register_func = Init, 213 .nm_modname = "entry", 214 .nm_priv = ((void *)0), 215 .reserved = {0}, 216}; 217 218extern "C" __attribute__((constructor)) void RegisterEntryModule(void) 219{ 220 napi_module_register(&demoModule); 221} 222``` 223在index.d.ts文件中怎添加如下代码: 224```index.d.ts 225export const test:() => number; 226``` 227 228### 场景二、在Native侧向应用主循环抛fd事件,接口无法生效 229 230由于应用主循环仅仅接收fd事件,在监听了uvloop中的backend_fd后,只有该fd事件被触发才会执行一次`uv_run`。这就意味着,在应用主循环中调用uv接口,如果不触发一次fd事件,`uv_run`将永远不会被执行,最后导致libuv的接口正常调用时不生效(仅当应用中没有触发uvloop中的fd事件时)。 231 232**错误示例:** 233 234我们以`uv_poll_start`接口举例,来说明在OpenHarmony中,我们像使用原生libuv一样调用`uv_poll_start`接口时无法生效的问题。 235 236ArkTS侧: 237```typescript 238import { hilog } from '@kit.PerformanceAnalysisKit'; 239import testNapi from 'libentry.so' 240 241@Entry 242@Component 243struct Index { 244 build() { 245 Row() { 246 Column() { 247 Button("testClose") 248 .width('40%') 249 .fontSize('14fp') 250 .onClick(() => { 251 testNapi.testClose(); 252 }).margin(20) 253 }.width('100%') 254 }.height('100%') 255 } 256} 257``` 258Native侧: 259```cpp 260#include "napi/native_api.h" 261#include "uv.h" 262#define LOG_DOMAIN 0X0202 263#define LOG_TAG "MyTag" 264#include <hilog/log.h> 265#include <thread> 266#include <sys/eventfd.h> 267 268uv_loop_t* loop = nullptr; 269napi_value jsCb; 270int fd = -1; 271 272void poll_handler(uv_poll_t* handle,int status, int events) 273{ 274 OH_LOG_INFO(LOG_APP, "ohos poll print"); 275} 276 277static napi_value TestClose(napi_env env, napi_callback_info info) 278{ 279 std::thread::id this_id = std::this_thread::get_id(); 280 OH_LOG_INFO(LOG_APP, "ohos thread id : %{public}ld", this_id); 281 size_t argc = 1; 282 napi_value workBname; 283 284 napi_create_string_utf8(env, "test", NAPI_AUTO_LENGTH, &workBname); 285 286 napi_get_cb_info(env, info, &argc, &jsCb, nullptr, nullptr); 287 // 获取事件循环 288 napi_get_uv_event_loop(env, &loop); 289 // 创建一个eventfd 290 fd = eventfd(0, 0); 291 OH_LOG_INFO(LOG_APP, "fd is %{public}d",fd); 292 uv_poll_t* poll_handle = new uv_poll_t; 293 // 初始化一个poll句柄,并将其与eventfd关联 294 uv_poll_init(loop, poll_handle, fd); 295 // 开始监听poll事件 296 uv_poll_start(poll_handle, UV_READABLE, poll_handler); 297 // 创建一个新线程,向eventfd写入数据 298 std::thread mythread([](){ 299 for (int i = 0; i < 8; i++){ 300 int value = 10; 301 int ret = eventfd_write(fd, value); 302 if (ret == -1){ 303 OH_LOG_INFO(LOG_APP, "write failed!"); 304 continue; 305 } 306 } 307 }); 308 mythread.detach(); 309 return 0; 310} 311 312EXTERN_C_START 313static napi_value Init(napi_env env, napi_value exports) 314{ 315 napi_property_descriptor desc[] = {{"testClose", nullptr, TestClose, nullptr, nullptr, nullptr, napi_default, nullptr}}; 316 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 317 return exports; 318} 319EXTERN_C_END 320 321static napi_module demoModule = { 322 .nm_version = 1, 323 .nm_flags = 0, 324 .nm_filename = nullptr, 325 .nm_register_func = Init, 326 .nm_modname = "entry", 327 .nm_priv = ((void *)0), 328 .reserved = {0}, 329}; 330 331extern "C" __attribute__((constructor)) void RegisterEntryModule(void) 332{ 333 napi_module_register(&demoModule); 334} 335``` 336 337在index.d.ts增加如下代码: 338 339``` 340export const testClose:() => number; 341``` 342 343在上述代码中,流程如下: 344 3451. 首先通过`napi_get_uv_event_loop`接口获取到应用主线程的uvloop。 3462. 然后创建一个eventfd。 3473. 初始化uv_poll_t,并启动该句柄使其生效,在eventfd可读时触发回调函数`poll_handler`。 3484. 新开一个线程,向eventfd里写入字符。 349 350执行上述代码,poll_handler并不能正常打印。这是由于应用主线程是靠fd驱动来执行`uv_run`的,而非以UV_RUN_DEFAULT模式来进行循环。尽管uvloop中的backend_fd已经被event_handler监听,但是当执行`uv_poll_start`的时候,fd并未通过`epoll_ctl`加入到backend_fd中被其监听,**而是在下一次`uv_run`中的`uv__io_poll`这个函数才会执行`epoll_ctl`函数。因此,如果应用进程中没有其他触发backend_fd事件的时候,libuv接口的正常使用可能不会达到开发者的预期。** 351 352**临时方案:** 353 354在当下的系统版本中,我们并不推荐开发者直接通过`napi_get_uv_event_loop`获取应用主线程的uvloop进行业务逻辑的开发。如果当前Node-API的接口无法满足开发者的开发需求,确有必要使用libuv来实现业务功能,为了使libuv接口在主线程上生效,开发者可以在调用类似*uv_xxx_start*后,执行一次`uv_async_send`的方式来主动触发应用主线程执行一次`uv_run`。这样可以保证该接口生效并正常执行。 355 356针对上述无法生效的代码示例,可以修改如下使其生效。 357 358ArkTS侧: 359```typescript 360import { hilog } from '@kit.PerformanceAnalysisKit'; 361import testNapi from 'libentry.so' 362 363@Entry 364@Component 365struct Index { 366 build() { 367 Row() { 368 Column() { 369 Button("testClose") 370 .width('40%') 371 .fontSize('14fp') 372 .onClick(() => { 373 testNapi.testClose(); 374 }).margin(20) 375 }.width('100%') 376 }.height('100%') 377 } 378} 379``` 380Native侧: 381```cpp 382#include "napi/native_api.h" 383#include "uv.h" 384#define LOG_DOMAIN 0x0202 385#define LOG_TAG "MyTag" 386#include <hilog/log.h> 387#include <thread> 388#include <sys/eventfd.h> 389 390uv_loop_t* loop = nullptr; 391napi_value jsCb; 392int fd = -1; 393 394void poll_handler(uv_poll_t* handle,int status, int events) 395{ 396 OH_LOG_INFO(LOG_APP, "ohos poll print"); 397} 398 399static napi_value TestClose(napi_env env, napi_callback_info info) 400{ 401 std::thread::id this_id = std::this_thread::get_id(); 402 OH_LOG_INFO(LOG_APP, "ohos thread id : %{public}ld", this_id); 403 size_t argc = 1; 404 napi_value workBName; 405 406 napi_create_string_utf8(env, "test", NAPI_AUTO_LENGTH, &workBName); 407 408 napi_get_cb_info(env, info, &argc, &jsCb, nullptr, nullptr); 409 410 napi_get_uv_event_loop(env, &loop); 411 412 fd = eventfd(0, 0); 413 OH_LOG_INFO(LOG_APP, "fd is %{public}d",fd); 414 uv_poll_t* poll_handle = new uv_poll_t; 415 uv_poll_init(loop, poll_handle, fd); 416 uv_poll_start(poll_handle, UV_READABLE, poll_handler); 417 418 // 主动触发一次fd事件,让主线程执行一次uv_run 419 uv_async_send(&loop->wq_async); 420 421 std::thread mythread([](){ 422 for (int i = 0; i < 8; i++){ 423 int value = 10; 424 int ret = eventfd_write(fd, value); 425 if (ret == -1){ 426 OH_LOG_INFO(LOG_APP, "write failed!"); 427 continue; 428 } 429 } 430 }); 431 mythread.detach(); 432 return 0; 433} 434 435EXTERN_C_START 436static napi_value Init(napi_env env, napi_value exports) 437{ 438 napi_property_descriptor desc[] = {{"testClose", nullptr, TestClose, nullptr, nullptr, nullptr, napi_default, nullptr}}; 439 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 440 return exports; 441} 442EXTERN_C_END 443 444static napi_module demoModule = { 445 .nm_version = 1, 446 .nm_flags = 0, 447 .nm_filename = nullptr, 448 .nm_register_func = Init, 449 .nm_modname = "entry", 450 .nm_priv = ((void *)0), 451 .reserved = {0}, 452}; 453 454extern "C" __attribute__((constructor)) void RegisterEntryModule(void) 455{ 456 napi_module_register(&demoModule); 457} 458``` 459在index.d.ts增加如下代码: 460 461``` 462export const testClose:() => number; 463``` 464 465## libuv使用指导 466 467**重要:libuv NDK中所有依赖`uv_run`的接口在当前系统的应用主循环中无法及时生效,并且可能会导致卡顿掉帧的现象。因此不建议直接在JS主线程上使用libuv NDK接口,对于异步任务执行及与使用线程安全函数与主线程通信,开发者可以直接调用Node-API接口来实现相关功能。** 468 469### libuv接口与Node-API接口对应关系 470 471当前OpenHarmony提供了一些Node-API接口,可以替换libuv接口的使用。主要包括异步任务相关接口,线程安全的函数调用接口。 472 473**1. 异步任务接口** 474 475当开发者需要执行一个比较耗时的操作但又不希望阻塞主线程执行时,libuv提供了底层接口`uv_queue_work`帮助开发者在异步线程中执行耗时操作,然后将结果回调到主线程上进行处理。 476 477在Node-API中,通常可以通过[napi_async_work](../../napi/use-napi-asynchronous-task.md)相关函数来实现异步开发的功能。 478 479相关函数为: 480 481```cpp 482/** 483* @brief 创建一个新的异步工作 484* 485* @param env 指向当前环境的指针 486* @param async_resource 可选的资源对象,用于跟踪异步操作 487* @param async_resource_name 可选的字符串,用于描述异步资源 488* @param execute 一个回调函数,它将在一个新的线程中执行异步操作 489* @param complete 一个回调函数,它将在异步操作完成后被调用 490* @param data 用户定义的数据,它将被传递给execute和complete回调函数 491* @param result 指向新创建的异步工作的指针 492*/ 493napi_status napi_create_async_work(napi_env env, 494 napi_value async_resource, 495 napi_value async_resource_name, 496 napi_async_execute_callback execute, 497 napi_async_complete_callback complete, 498 void* data, 499 napi_async_work* result); 500 501/** 502* @brief 将异步工作添加到队列中 503* 504* @param env 指向当前环境的指针 505* @param work 指向异步工作的指针 506*/ 507napi_status napi_queue_async_work(napi_env env, napi_async_work work); 508 509/** 510* @brief 删除异步工作 511* 512* @param env 指向当前环境的指针 513* @param work 指向异步工作的指针 514*/ 515napi_status napi_delete_async_work(napi_env env, napi_async_work work); 516``` 517 518**2. 跨线程共享和调用的线程安全函数** 519 520当开发者想在任意子线程传递某个回调函数到应用主线程上执行时,libuv的实现方式一般使用`uv_async_t`句柄用于线程间通信。 521 522相关函数包含: 523 524- uv_async_init() 525- uv_async_send() 526 527Node-API与之对应的接口为[napi_threadsafe_function](../../napi/use-napi-thread-safety.md)相关函数。 528 529相关函数: 530 531```cpp 532/** 533* @brief 用于创建一个线程安全的函数,该函数可以在多个线程中调用,而不需要担心数据竞争或其他线程安全问题 534* 535* @param env 指向NAPI环境的指针,用于创建和操作Javascript值 536* @param func 指向JavaScript函数的指针 537* @param async_resource 异步资源,通常是一个表示异步操作的对象 538* @param async_resource_name 指向资源名称的指针,这个名称将用于日志和调试 539* @param max_queue_size 一个整数,表示队列的最大大小,当队列满时,新的调用将被丢弃 540* @param initial_thread_count 无符号整数,表示在创建线程安全函数时,初始的线程数量 541* @param thread_finalize_data 一个指向在所有线程之前需要清理的数据 542* @param napi_finalize thread_finalize_cb 回调函数,当所有线程完成时被调用,用于清理资源 543* @param context 指向上下文的指针,这个上下文将被传递给call_js_func函数 544* @param call_js_cb 指向回调函数的指针,这个函数将在Javascript函数被调用时被调用 545* @param result 指向napi_threadsafe_function结构的指针,这个结构将被填充为新创建的线程安全函数 546*/ 547napi_status napi_create_threadsafe_function(napi_env env, 548 napi_value func, 549 napi_value async_resource, 550 napi_value async_resource_name, 551 size_t max_queue_size, 552 size_t initial_thread_count, 553 void* thread_finalize_data, 554 napi_finalize thread_finalize_cb, 555 void* context, 556 napi_threadsafe_function_call_js call_js_cb, 557 napi_threadsafe_function* result); 558 559/** 560* @brief 获取一个线程安全的函数 561* 562* @param function 指向线程安全函数的指针 563*/ 564napi_status napi_acquire_threadsafe_function(napi_threadsafe_function function); 565 566/** 567* @brief 调用一个线程安全的函数 568* @param function 指向线程安全函数的指针 569* @param data 用户数据 570* @param is_blocking 枚举值,它决定调用JavaScript函数是阻塞的还是非阻塞的 571*/ 572napi_status napi_call_threadsafe_function(napi_threadsafe_function function, 573 void* data, 574 napi_threadsafe_function_call_mode is_blocking); 575/** 576* @brief 释放一个线程安全的函数 577* 578* @param function 指向线程安全函数的指针 579* @param is_blocking 枚举值,它决定调用JavaScript函数是阻塞的还是非阻塞的 580*/ 581napi_status napi_release_threadsafe_function(napi_threadsafe_function function, 582 napi_threadsafe_function_call_mode is_blocking); 583 584``` 585 586除此之外,如果开发者需要libuv其他原生接口来实现业务功能,为了让开发者正确使用libuv提供的接口能力,避免因为错误使用而陷入到问题当中。在后续章节,我们将逐步介绍libuv的一些基本概念和OpenHarmony系统中常用函数的正确使用方法,它仅仅可以保证开发者使用libuv接口的时候不会出现应用进程崩溃等现象。另外,我们还统计了在当前应用主线程上可以正常使用的接口,以及无法在应用主线程上使用的接口。 587 588### 接口汇总说明 589 590| 接口类型 | 接口汇总 | 591| ---- | ---- | 592| [loop概念及相关接口](#libuv中的事件循环) | uv_loop_init | 593| [loop概念及相关接口](#libuv中的事件循环) | uv_loop_close | 594| [loop概念及相关接口](#libuv中的事件循环) | uv_default_loop | 595| [loop概念及相关接口](#libuv中的事件循环) | uv_run | 596| [loop概念及相关接口](#libuv中的事件循环) | uv_loop_alive | 597| [loop概念及相关接口](#libuv中的事件循环) | uv_stop | 598| [Handle概念及相关接口](#libuv中的handles和requests) | uv_poll\_\* | 599| [Handle概念及相关接口](#libuv中的handles和requests) | uv_timer\_\* | 600| [Handle概念及相关接口](#libuv中的handles和requests) | uv_async\_\* | 601| [Handle概念及相关接口](#libuv中的handles和requests) | uv_signal\_\* | 602| [Handle概念及相关接口](#libuv中的handles和requests) | uv_fs\_\* | 603| [Request概念及相关接口](#libuv中的handles和requests) | uv_random | 604| [Request概念及相关接口](#libuv中的handles和requests) | uv_getaddrinfo | 605| [Request概念及相关接口](#libuv中的handles和requests) | uv_getnameinfo | 606| [Request概念及相关接口](#libuv中的handles和requests) | uv_queue_work | 607| [线程间通信原理及相关接口](#线程间通信) | uv_async_init | 608| [线程间通信原理及相关接口](#线程间通信) | uv_async_send | 609| [线程池概念及相关接口](#线程池) | uv_queue_work | 610 611### libuv单线程约束 612 613在OpenHarmony中使用libuv时,**务必注意:使用`uv_loop_init`接口初始化loop的线程和调用`uv_run`的线程应保持一致,称为loop线程,并且对uvloop的所有非线程安全操作,均需保证与loop同线程,否则将会有发生crash的风险**。OpenHarmony对libuv的使用有更严格的约束,对于非线程安全的函数,libuv将实现多线程检测机制,检测到多线程问题后输出警告日志。为了确保检测机制的准确性,协助开发者规避uv接口的不规范使用,我们建议在创建事件循环与执行uv_run始终保持在同一线程。根据loop来源的不同,可分为两种情况,即开发者创建loop和从env获取loop。 614 615**1. 开发者创建loop** 616 617开发者可以通过调用`uv_loop_new`创建loop或者`uv_loop_init`接口初始化loop,loop的生命周期由开发者自行维护。在这种情况下,如前文所述,需要保证`uv_run`执行在与创建/初始化loop操作相同的线程上,即loop线程上。此外,其余非线程安全操作,如timer相关操作等,均需要在loop线程上进行。 618 619如果因为业务需要,必须在其他线程往loop线程抛任务,请使用`uv_async_send`函数:即在async句柄初始化时,注册一个回调函数,并在该回调中实现相应的操作,当调用`uv_async_send`时,在主线程上执行该回调函数。 620 621ArkTS侧: 622 623```typescript 624import { hilog } from '@kit.PerformanceAnalysisKit'; 625import testNapi from 'libentry.so' 626 627@Entry 628@Component 629struct Index { 630 build() { 631 Row() { 632 Column() { 633 Button("TestTimerAsync") 634 .width('40%') 635 .fontSize('14fp') 636 .onClick(() => { 637 testNapi.testTimerAsync(); // 初始化async句柄 638 }).margin(20) 639 640 Button("TestTimerAsyncSend") 641 .width('40%') 642 .fontSize('14fp') 643 .onClick(() => { 644 testNapi.testTimerAsyncSend(); // 子线程调用uv_async_send提交定时器任务 645 }).margin(20) 646 }.width('100%') 647 }.height('100%') 648 } 649} 650``` 651 652Native侧: 653 654```cpp 655#include <napi/native_api.h> 656#include <uv.h> 657#define LOG_DOMAIN 0x0202 658#define LOG_TAG "MyTag" 659#include "hilog/log.h" 660#include <thread> 661 662uv_async_t* async = new uv_async_t; 663bool cond1 = false; 664bool cond2 = false; 665 666// 使用技巧:在使用loop时, 需要特别注意uv_stop函数的使用, 开发者需要确保uv_stop前 667// 通知与loop相关的所有线程的handle都关闭, 参考stop_loop函数的实现 668int stop_loop(uv_loop_t* loop) 669{ 670 uv_stop(loop); 671 auto const ensure_close = [](uv_handle_t* handle, void*) { 672 if (uv_is_closing(handle)) { 673 return; 674 } else { 675 uv_close(handle, nullptr); 676 } 677 }; 678 // 遍历所有句柄, 如果handle处于活跃状态, 调用ensure_close 679 uv_walk(loop, ensure_close, nullptr); 680 // 继续运行uv_run, 直到loop中不存在活跃的句柄和请求为止 681 while(true) { 682 if (uv_run(loop, UV_RUN_DEFAULT) == 0) { 683 break; 684 } 685 } 686 687 // 最后检查loop状态 688 if (uv_loop_alive(loop) != 0) { 689 return -1; 690 } 691 return 0; 692} 693 694// 执行创建定时器操作 695void async_cb(uv_async_t* handle) { 696 auto loop = handle->loop; 697 uv_timer_t* timer = new uv_timer_t; 698 uv_timer_init(loop, timer); 699 700 // 在适当的时机关闭async句柄 701 if (cond2) { 702 uv_close((uv_handle_t*)handle, [](uv_handle_t* handle){ 703 delete (uv_async_t*)handle; 704 }); 705 return; 706 } 707 708 uv_timer_start(timer, 709 [](uv_timer_t* timer){ 710 // do something 711 // 在适当的时机停掉timer 712 if (cond1) { 713 uv_timer_stop(timer); 714 uv_close((uv_handle_t*)timer, [](uv_handle_t* handle){ 715 delete(uv_timer_t*)handle; 716 }); 717 } 718 }, 719 100, 100); 720} 721 722// 初始化async句柄, 绑定对应的回调函数 723static napi_value TestTimerAsync(napi_env env, napi_callback_info info) { 724 std::thread t([](){ // A线程,loop线程 725 uv_loop_t* loop = new uv_loop_t; 726 // 开发者自己创建loop, 请注意维护loop的生命周期 727 uv_loop_init(loop); 728 // 初始化一个async句柄, 注册回调函数 729 uv_async_init(loop, async, async_cb); 730 // 让loop开始运行 731 uv_run(loop, UV_RUN_DEFAULT); 732 // 清理所有的handle 733 stop_loop(loop); 734 // 释放loop 735 uv_loop_close(loop); 736 delete loop; 737 }); 738 t.detach(); 739 return 0; 740} 741 742// 在另一个线程上调用uv_async_send函数 743static napi_value TestTimerAsyncSend(napi_env env, napi_callback_info info) 744{ 745 std::thread t1([](){ // B线程 746 uv_async_send(async); // 调用uv_async_send, 通知loop线程调用与async句柄绑定的timer_cb 747 uv_sleep(500); 748 // 修改cond1, 关闭timer handle 749 cond1 = true; 750 }); 751 752 std::thread t2([](){ // B线程 753 uv_sleep(1000); 754 // 修改cond2, 关闭async handle 755 cond2 = true; 756 uv_async_send(async); 757 }); 758 759 t1.detach(); 760 t2.detach(); 761 return 0; 762} 763 764EXTERN_C_START 765static napi_value Init(napi_env env, napi_value exports) 766{ 767 napi_property_descriptor desc[] = { 768 {"testTimerAsync", nullptr, TestTimerAsync, nullptr, nullptr, nullptr, napi_default, nullptr}, 769 {"testTimerAsyncSend", nullptr, TestTimerAsyncSend, nullptr, nullptr, nullptr, napi_default, nullptr}, 770 }; 771 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 772 return exports; 773} 774EXTERN_C_END 775 776static napi_module demoModule = { 777 .nm_version = 1, 778 .nm_flags = 0, 779 .nm_filename = nullptr, 780 .nm_register_func = Init, 781 .nm_modname = "entry", 782 .nm_priv = ((void *)0), 783 .reserved = {0}, 784}; 785 786extern "C" __attribute__((constructor)) void RegisterEntryModule(void) 787{ 788 napi_module_register(&demoModule); 789} 790``` 791 792在index.d.ts增加如下代码: 793 794``` 795export const testTimerAsync:() => number; 796export const testTimerAsyncSend:() => number; 797``` 798 799**2. 从env获取loop** 800 801开发者使用`napi_get_uv_event_loop`接口从env获取到的loop一般是系统创建的JS主线程的事件循环,因此应当避免在子线程中调用非线程安全函数。 802 803如因业务需要,必须在非loop线程上调用非线程安全函数,请使用线程安全函数`uv_async_send`将任务提交到loop线程。即定义一个uv_async_t*类型的句柄,初始化该句柄的时候,将需要在子线程调用的非线程安全函数在对应的async_cb中调用,然后在非loop线程上调用`uv_async_send`函数,并回到loop线程上执行async_cb。请参考[libuv中的handles和requests](#libuv中的handles和requests)章节关于**正确使用timer示例**的场景二内容。 804 805### 线程安全函数 806 807在libuv中,由于涉及到大量的异步任务,稍有不慎就会陷入到多线程问题中。在这里,我们对libuv中常用的线程安全函数和非线程安全函数做了汇总。若开发者在多线程编程中调用了非线程安全的函数,势必要对其进行加锁保护或者保证代码的正确运行时序,否则将陷入到crash问题中。 808 809线程安全函数: 810 811- uv_async_send():向异步句柄发送信号,可以在任何线程中调用。 812- uv_thread_create():创建一个新线程并执行指定的函数,可以在任何线程中调用。 813- 锁相关的操作,如uv\_mutex\_lock()、uv\_mutex\_unlock()等等。 814 815**提示:所有形如uv_xxx_init的函数,即使它是以线程安全的方式实现的,但使用时要注意,避免多个线程同时调用uv_xxx_init,否则它依旧会引起多线程资源竞争的问题。最好的方式是在事件循环线程中调用该函数。** 816 817**注:`uv_async_send`函数被调用后,回调函数是被异步触发的。如果调用了多次`uv_async_send`,libuv只保证至少有一次回调会被执行。这就可能导致一旦对同一句柄触发了多次`uv_async_send`,libuv对回调的处理可能会违背开发者的预期。多次对同一个async句柄进行send操作,还会导致任意两次相同句柄send操作之间提交的的其他async_cb任务丢失。** 而在Native侧,可以保证回调的执行次数和开发者调用`napi_call_threadsafe_function`的次数保持一致。 818 819非线程安全函数: 820 821- uv\_os\_unsetenv():删除环境变量 822- uv\_os\_setenv():设置环境变量 823- uv\_os\_getenv():获取环境变量 824- uv\_os\_environ():检索所有的环境变量 825- uv\_os\_tmpdir():获取临时目录 826- uv\_os\_homedir():获取家目录 827 828### libuv中的事件循环 829 830事件循环是libuv中最核心的一个概念,loop负责管理整个事件循环的所有资源,它贯穿于整个事件循环的生命周期。通常将`uv_run`所在的线程称为该事件循环的主线程。 831 832**1. 事件循环运行的三种方式** 833 834`UV_RUN_DEFAULT`:默认轮询方式,该模式将会一直运行下去,直到loop中没有活跃的句柄和请求。 835 836`UV_RUN_ONCE`:一次轮询模式,如果pending_queue中有回调函数,则执行,然后跳过`uv__io_poll`函数。此模式默认认为loop中一定有事件发生。 837 838`UV_RUN_NOWAIT`:非阻塞模式,该模式下不会执行pending_queue,而是直接执行一次I/O轮询(`uv__io_poll`)。 839 840**2. 常用接口** 841 842```cpp 843int uv_loop_init(uv_loop_t* loop); 844``` 845 846 对loop进行初始化。 847 848```cpp 849int uv_loop_close(uv_loop_t* loop); 850``` 851 852 关闭loop,该函数只有在loop中所有的句柄和请求都关闭后才能成功返回,否则将返回UV_EBUSY。 853 854```cpp 855int uv_loop_delete(uv_loop_t* loop); 856``` 857 858释放loop,该接口会先调用`uv_loop_close`,然后再将loop释放掉。在OpenHarmony平台上,由于assert函数不生效,因此不论`uv_loop_close`函数是否成功清理loop上的资源,都会将loop释放掉。开发者使用该接口时,请务必确保在loop线程退出时,loop上的资源可以被正确释放,即挂在loop上的handle和request均被关闭,否则会导致资源泄漏。**开发者使用该接口时务必格外谨慎,建议非必要不使用。** 859 860```cpp 861uv_loop_t* uv_default_loop(void); 862``` 863 864 该函数创建一个进程级的loop。在OpenHarmony中,由于目前的应用主循环及其他JS工作线程还存在着libuv的loop。因此我们不建议开发者使用该函数来创建loop并实现业务功能。 865 866```cpp 867int uv_run(uv_loop_t* loop, uv_run_mode mode); 868``` 869 870 启动事件循环。运行模式可查看事件循环运行的三种方式。 871 872```cpp 873int uv_loop_alive(uv_loop_t loop); 874``` 875 876 判断loop是否处于活跃状态。 877 878```cpp 879void uv_stop(uv_loop_t* loop); 880``` 881 882 该函数用来停止一个事件循环,在loop的下一次迭代中才会停止。如果该函数发生在I/O操作之前,将不会阻塞而是直接跳过`uv__io_poll`。 883 884 885### libuv中的handles和requests 886 887handle表示一个持久性的对象,通常挂载到loop中对应的handle_queue队列上。如果handle处于活跃状态,每次`uv_run`都会处理handle中的回调函数。 888 889request表示一个短暂性的请求,一个request只触发一次回调操作。 890 891下面是OpenHarmony系统中最常用的几个Handles和Requests: 892 893```cpp 894/* Handle Type */ 895typedef struct uv_handle_s uv_handle_t; 896typedef struct uv_timer_s uv_timer_t; 897typedef struct uv_async_s uv_async_t; 898typedef struct uv_signal_s uv_signal_t; 899 900/* Request Type */ 901typedef struct uv_req_s uv_req_t; 902typedef struct uv_work_s uv_work_t; 903typedef struct uv_fs_s uv_fs_t; 904``` 905 906**注:在handles中,uv_xxx_t继承了uv_handle_t;在requests中,uv_work_t继承了uv_req_t。** 907 908对于libuv中的handles,对其有正确的认识并管理好它的生命周期至关重要。handle作为一个长期存在于loop中的句柄,在使用中,开发者应遵循下面的原则: 909 9101. 句柄的初始化工作应在事件循环的线程中进行。 9112. 若由于业务问题,句柄需要在其他工作线程初始化,在使用之前用原子变量判断是否初始化完成。 9123. 句柄在确定后续不再使用后,调用`uv_close`将句柄从loop中摘除。 913 914在这里,需要特别说明一下`uv_close`的使用方法。`uv_close`被用来关闭一个handle,但是关闭handle的动作是异步的。函数原型为: 915 916```cpp 917void uv_close(uv_handle_t* handle, uv_close_cb close_cb) 918``` 919 920 handle:要关闭的句柄。 921 close_cb:处理该句柄的函数,用来进行内存管理等操作。 922 923调用`uv_close`后,首先将要关闭的handle挂载到loop的closing_handles队列上,然后等待loop所在线程运行`uv__run_closing_handles`函数。最后回调函数close_cb将会在loop的下一次迭代中执行。因此,释放内存等操作应该在close_cb中进行。并且这种异步的关闭操作会带来多线程问题,开发者需要谨慎处理`uv_close`的时序问题,并且保证在close_cb执行之前handles的生命周期。 924 925**Tips**:在[libuv官方文档](http://libuv.org/)中,有个经验法则需要在此提示一下。原文翻译:如果 uv_foo_t 类型的句柄具有 `uv_foo_start()` 函数,则从调用该函数的那一刻起,它就处于活动状态。 同样,`uv_foo_stop()`再次停用句柄。 926 927> **注意** 928> 929> 1. 所有的handle关闭前必须要调用`uv_close`,所有的内存操作都要在`uv_close`的close_cb中执行。 930> 931> 2. 所有的handle操作都不能通过获取其他线程loop的方式,在非loop线程上调用。 932 933对于libuv中的requests,开发者需要确保在进行异步任务提交时,**通过动态申请的request,要在loop所在线程执行的complete回调函数中释放**。用uv_work_t举例,代码可参考如下: 934 935```cpp 936uv_work_t* work = new uv_work_t; 937uv_queue_work(loop, work, [](uv_work_t* req) { 938 // 异步操作 939}, [](uv_work_t* req, int status) { 940 // 回调操作 941 delete req; 942}); 943``` 944 945### libuv timer使用规范 946 947使用libuv timer需要遵守如下约定: 948 9491. 请不要在多个线程中使用libuv的接口(`uv_timer_start`、`uv_timer_stop`和`uv_timer_again`)同时操作同一个loop的timer heap,否则将导致崩溃,如果想要使用libuv的接口操作定时器,请**保持在与当前env绑定的loop所在线程上操作**; 9502. 如因业务需求往指定线程抛定时器,请使用`uv_async_send`线程安全函数实现。 951 952**1. 错误使用timer示例** 953 954以下错误示例中,由于在多个线程操作同一个loop的timer heap,崩溃率极高。 955 956ArkTS侧: 957 958```typescript 959import { hilog } from '@kit.PerformanceAnalysisKit'; 960import testNapi from 'libentry.so' 961 962function waitforRunner(): number { 963 "use concurrent" 964 hilog.info(0xff, "testTag", "executed"); 965 return 0; 966} 967 968@Entry 969@Component 970struct Index { 971 build() { 972 Row() { 973 Column() { 974 Button("TimerTest") 975 .width('40%') 976 .fontSize('14fp') 977 .onClick(() => { 978 let i: number = 20; 979 while (i--) { 980 setTimeout(waitforRunner, 200); 981 testNapi.testTimer(); 982 } 983 }).margin(20) 984 }.width('100%') 985 }.height('100%') 986 } 987} 988``` 989 990Native C++侧: 991 992```cpp 993#include <napi/native_api.h> 994#include <uv.h> 995#define LOG_DOMAIN 0x0202 996#define LOG_TAG "MyTag" 997#include "hilog/log.h" 998#include <thread> 999#include <unistd.h> 1000 1001static napi_value TestTimer(napi_env env, napi_callback_info info) 1002{ 1003 uv_loop_t* loop = nullptr; 1004 uv_timer_t* timer = new uv_timer_t; 1005 1006 napi_get_uv_event_loop(env, &loop); 1007 uv_timer_init(loop, timer); 1008 std::thread t1([&loop, &timer](){ 1009 uv_timer_start(timer, [](uv_timer_t* timer){ 1010 uv_timer_stop(timer); 1011 }, 1000, 0); 1012 }); 1013 1014 t1.detach(); 1015 return 0; 1016} 1017 1018EXTERN_C_START 1019static napi_value Init(napi_env env, napi_value exports) 1020{ 1021 napi_property_descriptor desc[] = { 1022 {"testTimer", nullptr, TestTimer, nullptr, nullptr, nullptr, napi_default, nullptr}, 1023 }; 1024 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 1025 return exports; 1026} 1027EXTERN_C_END 1028 1029static napi_module demoModule = { 1030 .nm_version = 1, 1031 .nm_flags = 0, 1032 .nm_filename = nullptr, 1033 .nm_register_func = Init, 1034 .nm_modname = "entry", 1035 .nm_priv = ((void *)0), 1036 .reserved = {0}, 1037}; 1038 1039extern "C" __attribute__((constructor)) void RegisterEntryModule(void) 1040{ 1041 napi_module_register(&demoModule); 1042} 1043``` 1044 1045在index.d.ts增加如下代码: 1046 1047```typescript 1048export const testTimer:() => number; 1049``` 1050 1051**2. 正确使用timer示例** 1052 1053**场景一:** 在上述场景中,需保证在JS主线程上进行timer的相关操作。将上述TestTimer函数的代码做如下修改,便可以避免崩溃发生。 1054 1055```cpp 1056static napi_value TestTimer(napi_env env, napi_callback_info info) 1057{ 1058 uv_loop_t* loop = nullptr; 1059 uv_timer_t* timer = new uv_timer_t; 1060 1061 napi_get_uv_event_loop(env, &loop); 1062 uv_timer_init(loop, timer); 1063 uv_timer_start(timer, [](uv_timer_t* timer){ 1064 uv_timer_stop(timer); 1065 }, 1000, 0); 1066 1067 return 0; 1068} 1069``` 1070 1071**场景二:** 如果需要在指定的子线程抛定时器,请使用线程安全函数`uv_async_send`实现。 1072 1073ArkTS侧: 1074```typescript 1075import { hilog } from '@kit.PerformanceAnalysisKit'; 1076import testNapi from 'libentry.so' 1077 1078@Entry 1079@Component 1080struct Index { 1081 build() { 1082 Row() { 1083 Column() { 1084 Button("TestTimerAsync") 1085 .width('40%') 1086 .fontSize('14fp') 1087 .onClick(() => { 1088 testNapi.testTimerAsync(); // 初始化async句柄 1089 }).margin(20) 1090 1091 Button("TestTimerAsyncSend") 1092 .width('40%') 1093 .fontSize('14fp') 1094 .onClick(() => { 1095 testNapi.testTimerAsyncSend(); // 子线程调用uv_async_send提交定时器任务 1096 }).margin(20) 1097 }.width('100%') 1098 }.height('100%') 1099 } 1100} 1101``` 1102 1103Native侧: 1104 1105```c++ 1106#include <napi/native_api.h> 1107#include <uv.h> 1108#define LOG_DOMAIN 0x0202 1109#define LOG_TAG "MyTag" 1110#include "hilog/log.h" 1111#include <thread> 1112#include <unistd.h> 1113uv_async_t* async = new uv_async_t; 1114 1115// 执行创建定时器操作 1116void async_cb(uv_async_t* handle) 1117{ 1118 auto loop = handle->loop; 1119 uv_timer_t* timer = new uv_timer_t; 1120 uv_timer_init(loop, timer); 1121 1122 uv_timer_start(timer, [](uv_timer_t* timer){ 1123 uv_timer_stop(timer); 1124 }, 1000, 0); 1125} 1126 1127// 初始化async句柄,绑定对应的回调函数 1128static napi_value TestTimerAsync(napi_env env, napi_callback_info info) 1129{ 1130 uv_loop_t* loop = nullptr; 1131 napi_get_uv_event_loop(env, &loop); 1132 uv_async_init(loop, async, async_cb); 1133 return 0; 1134} 1135 1136static napi_value TestTimerAsyncSend(napi_env env, napi_callback_info info) 1137{ 1138 std::thread t([](){ 1139 uv_async_send(async); // 在任意子线程中调用uv_async_send,通知主线程调用与async绑定的timer_cb 1140 }); 1141 t.detach(); 1142 return 0; 1143} 1144 1145EXTERN_C_START 1146static napi_value Init(napi_env env, napi_value exports) 1147{ 1148 napi_property_descriptor desc[] = { 1149 {"testTimerAsync", nullptr, TestTimerAsync, nullptr, nullptr, nullptr, napi_default, nullptr}, 1150 {"testTimerAsyncSend", nullptr, TestTimerAsyncSend, nullptr, nullptr, nullptr, napi_default, nullptr}, 1151 }; 1152 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 1153 return exports; 1154} 1155EXTERN_C_END 1156 1157static napi_module demoModule = { 1158 .nm_version = 1, 1159 .nm_flags = 0, 1160 .nm_filename = nullptr, 1161 .nm_register_func = Init, 1162 .nm_modname = "entry", 1163 .nm_priv = ((void *)0), 1164 .reserved = {0}, 1165}; 1166 1167extern "C" __attribute__((constructor)) void RegisterEntryModule(void) 1168{ 1169 napi_module_register(&demoModule); 1170} 1171``` 1172 1173在index.d.ts增加如下代码: 1174 1175``` 1176export const testTimerAsync:() => number; 1177export const testTimerAsyncSend:() => number; 1178``` 1179 1180### 线程间通信 1181 1182上面简单介绍了一些libuv中的基本概念,在这里我们将着重介绍libuv中的线程间通信。 1183 1184libuv的线程间通信是通过uv_async_t句柄来进行的,相关函数如下: 1185 1186```cpp 1187int uv_async_init(uv_loop_t* loop, uv_async_t* handle, uv_async_cb async_cb) 1188``` 1189 1190 loop:事件循环loop。 1191 1192 handle:线程间通信句柄。 1193 1194 async_cb:回调函数。 1195 1196 返回:成功,返回0。失败,返回错误码。 1197 1198```cpp 1199int uv_async_send(uv_async_t* handle) 1200``` 1201 1202 handle:线程间通信句柄。 1203 1204 返回:成功,返回0。失败,返回错误码。 1205> 说明 1206> 1207> 1. uv_async_t从调用`uv_async_init`开始后就一直处于活跃状态,除非用`uv_close`将其关闭。 1208> 1209> 2. uv_async_t的执行顺序严格按照`uv_async_init`的顺序,而非通过`uv_async_send`的顺序来执行的。因此按照初始化的顺序来管理好时序问题是必要的。 1210 1211 1212 1213示例代码: 1214 1215```cpp 1216#include <iostream> 1217#include <thread> 1218#include "uv.h" 1219 1220uv_loop_t* loop = nullptr; 1221uv_async_t* async = nullptr; 1222int g_counter = 10; 1223 1224void async_handler(uv_async_t* handle) 1225{ 1226 std::cout << "ohos async print" << std::endl; 1227 if (--g_counter == 0) { 1228 // 调用uv_close关闭async,在主循环中释放内存。 1229 uv_close((uv_handle_t*)async, [](uv_handle_t* handle) { 1230 std::cout << "delete async" << std::endl; 1231 delete (uv_async_t*)handle; 1232 }); 1233 } 1234} 1235 1236int main() 1237{ 1238 loop = uv_default_loop(); 1239 async = new uv_async_t; 1240 uv_async_init(loop, async, async_handler); 1241 std::thread subThread([]() { 1242 for (int i = 0; i < 10; i++) { 1243 usleep(100); // 避免多次调用uv_async_send只执行一次 1244 std::cout << i << "th: subThread triggered" << std::endl; 1245 uv_async_send(async); 1246 } 1247 }); 1248 subThread.detach(); 1249 return uv_run(loop, UV_RUN_DEFAULT); 1250} 1251``` 1252 1253该示例代码仅仅描述了一个简单的场景,步骤如下: 1254 12551. 在主线程中初始化async句柄 12562. 新建一个子线程,在里面每隔100毫秒触发一次`uv_async_send`。10次以后调用`uv_close`关闭async句柄。 12573. 在主线程运行事件循环。 1258 1259可以看到,每触发一次,主线程都会执行一次回调函数。 1260 1261``` 12620th:subThread triggered 1263ohos async print 12641th:subThread triggered 1265ohos async print 12662th:subThread triggered 1267ohos async print 12683th:subThread triggered 1269ohos async print 12704th:subThread triggered 1271ohos async print 12725th:subThread triggered 1273ohos async print 12746th:subThread triggered 1275ohos async print 12767th:subThread triggered 1277ohos async print 12788th:subThread triggered 1279ohos async print 12809th:subThread triggered 1281ohos async print 1282delete async 1283``` 1284 1285### 线程池 1286 1287线程池是libuv的一个核心功能,libuv中的线程池通过uv_loop_t中的成员变量wq_async来控制工作线程与主线程的通信。核心函数如下: 1288 1289```cpp 1290int uv_queue_work(uv_loop_t* loop, 1291 uv_work_t* req, 1292 uv_work_cb work_cb, 1293 uv_after_work_cb after_work_cb) 1294``` 1295 1296work_cb:提交给工作线程的任务。 1297 1298after_work_cb:loop所在线程要执行的回调函数。 1299 1300**注意:** work_cb与after_work_cb的执行有一个时序问题,只有work_cb执行完,通过`uv_async_send(loop->wq_async)`触发fd事件,loop所在线程在下一次迭代中才会执行after_work_cb。只有执行到after_work_cb时,与之相关的uv_work_t生命周期才算结束。 1301 1302**1. 异步任务提交** 1303 1304下图为原生libuv的线程池工作流程,图中流程已简化,默认句柄的pending标志为1,worker线程个数不代表线程池中线程的真实数量。 1305 1306 1307 1308**2. 异步任务提交注意事项** 1309 1310在OpenHarmony中,`uv_queue_work`函数在UI线程的工作流程为:将`work_cb`抛到FFRT对应优先级的线程池中,然后待FFRT调度执行该任务,并将`after_work_cb`抛到eventhandler对应优先级的event queue中,等待eventhandler调度并回到loop线程执行。需要注意的是,`uv_queue_work`调用完后,并不代表其中的任何一个任务执行完,仅代表将work_cb插入到FFRT对应优先级的线程池中。taskpool和jsworker线程的工作流程和原生libuv逻辑保持一致。 1311 1312对于一些特定场景,比如对内存开销敏感的场景中,同一个request可以重复使用,前提是保证同一类任务之间的顺序,并且要确保最后一次调用`uv_queue_work`时做好对该request的释放工作。 1313 1314```C 1315uv_work_t* work = new uv_work_t; 1316uv_queue_work(loop, work, [](uv_work_t* work) { 1317 //do something 1318 }, 1319 [](uv_work_t* work, int status) { 1320 // do something 1321 uv_queue_work(loop, work, [](...) {/* do something*/}, [](...) { 1322 //do something 1323 if (last_task) { // 最后一个任务执行完以后,释放该request 1324 delete work; 1325 } 1326 }); 1327 }, 1328 ) 1329``` 1330 1331**3. uv_queue_work使用约束** 1332 1333特别强调,开发者需要明确,`uv_queue_work`函数仅用于抛异步任务,**异步任务的execute回调被提交到线程池后会经过调度执行,因此并不保证多次提交的任务及其回调按照时序关系执行**。 1334 1335另外,`uv_queue_work`仅限于在loop线程中调用,这样不会有多线程安全问题。**请不要把uv_queue_work作为线程间通信的手段,即A线程获取到B线程的loop,并通过`uv_queue_work`抛异步任务的方式,把execute置为空任务,而把complete回调放在B线程中执行。** 这种方式不仅低效,而且还增加了发生故障时定位问题的难度。为了避免低效的任务提交,请使用[napi_threadsafe_function相关函数](../../napi/use-napi-thread-safety.md)。 1336 1337### OpenHarmony中libuv的使用现状 1338 1339当前OpenHarmony系统中涉及到libuv的线程主要有主线程、JS Worker线程、Taskpool中的TaskWorker线程以及IPC线程。除了主线程采用了eventhandler作为主循环,其他线程都是使用libuv中的UV_RUN_DEFAULT运行模式作为当前线程的事件主循环来执行任务。在主线程中,eventhandler通过fd驱动的方式来触发任务的执行,eventhandler监听了uv_loop中的backend_fd。当loop中有fd事件触发的时候,eventhandler会执行一次`uv_run`来执行一遍libuv中的任务。 1340 1341综上所述,开发者会发现这样一种现象:**同样的libuv接口在主线程上不生效,但在JS Worker线程中就没问题。这主要还是因为主线程上所有不通过触发fd来驱动的uv接口都不会得到及时的响应。** 1342 1343另外,在应用主线程中,所有的异步任务尽管最终都是通过libuv得到执行的。但是在当前系统中,[libuv的线程池已经对接到了FFRT中](https://gitee.com/openharmony/third_party_libuv/wikis/06-Wiki-%E6%8A%80%E6%9C%AF%E8%B5%84%E6%BA%90/%20libuv%E5%B7%A5%E4%BD%9C%E7%BA%BF%E7%A8%8B%E6%8E%A5%E5%85%A5FFRT%E6%96%B9%E6%A1%88%E5%88%86%E6%9E%90),任何抛向libuv的异步任务都会在FFRT的线程中得到调度。应用主线程的回调函数也通过PostTask接口插入到eventhandler的队列上。这就意味着FFRT线程上的异步任务完成后不再通过`uv_async_send`的方式触发主线程的回调。过程如下图: 1344 1345 1346 1347我们总结了五种类型的请求任务是直接可以按照正常用法在应用主循环中生效的: 1348 1349- uv_random_t 1350 1351 函数原型: 1352 1353```cpp 1354/** 1355* @brief 将一个工作请求添加到事件循环的队列中。 1356* 1357* @param loop 事件循环 1358* @param req 随机数请求 1359* @param buf 存储随机数的缓冲区 1360* @param buflen 缓冲区的长度 1361* @param flags 一个无符号整数,表示生成随机数的选项 1362* @param cb 随机数生成完成后的回调函数 1363* 1364* @return 成功返回0,失败返回错误码 1365*/ 1366int uv_random(uv_loop_t* loop, 1367 uv_random_t* req, 1368 void* buf, 1369 size_t buflen, 1370 unsigned flags, 1371 uv_random_cb cb); 1372``` 1373 1374- uv_work_t 1375 1376 函数原型: 1377 1378```cpp 1379/** 1380* @brief 将一个工作请求添加到事件循环的队列中。当事件循环在下一次迭代时,work_cb函数将会在一个新的线程中被调用。当work_cb函数完成时,after_work_cb函数将会在事件循环的线程中被调用。 1381* 1382* @param loop 事件循环 1383* @param req 工作请求 1384* @param work_cb 在新线程中被调用的函数 1385* @param after_work_cb 在事件循环线程中被调用的函数 1386* 1387* @return 成功返回0,失败返回-1 1388*/ 1389int uv_queue_work(uv_loop_t* loop, 1390 uv_work_t* req, 1391 uv_work_cb work_cb, 1392 uv_after_work_cb after_work_cb); 1393``` 1394 1395- uv_fs_t 1396 1397 文件类提供的所有异步接口,在应用主线程中都是可以生效的。主要有如下: 1398 1399```cpp 1400/** 1401* @brief 异步读取文件 1402* 1403* @param loop 事件循环 1404* @param req 文件操作请求 1405* @param file 文件描述符 1406* @param bufs 读取数据的缓冲区 1407* @param nbufs 缓冲区的数量 1408* @param off 文件的偏移量 1409* @param cb 完成后的回调函数 1410* @return 成功返回0,失败返回-1 1411*/ 1412int uv_fs_read(uv_loop_t* loop, uv_fs_t* req, 1413 uv_file file, 1414 const uv_buf_t bufs[], 1415 unsigned int nbufs, 1416 int64_t off, 1417 uv_fs_cb cb); 1418 1419/** 1420* @brief 异步打开文件 1421* 1422* @param loop 事件循环 1423* @param req 文件操作请求 1424* @param path 文件路径 1425* @param flags 打开文件的方式 1426* @param mode 文件权限 1427* @param cb 完成后的回调函数 1428* 1429* @return 成功返回0,失败返回-1 1430*/ 1431int uv_fs_open(uv_loop_t* loop, 1432 uv_fs_t* req, 1433 const char* path, 1434 int flags, 1435 int mode, 1436 uv_fs_cb cb); 1437 1438/** 1439* @brief 异步发送文件 1440* 1441* @param loop 事件循环 1442* @param req 文件操作请求 1443* @param out_fd 输出文件描述符 1444* @param in_fd 输入文件描述符 1445* @param off 文件的偏移量 1446* @param len 发送的长度 1447* @param cb 完成后的回调函数 1448* 1449* @return 成功返回0,失败返回-1 1450*/ 1451int uv_fs_sendfile(uv_loop_t* loop, 1452 uv_fs_t* req, 1453 uv_file out_fd, 1454 uv_file in_fd, 1455 int64_t off, 1456 size_t len, 1457 uv_fs_cb cb); 1458 1459/** 1460* @brief 异步写入文件 1461* 1462* @param loop 事件循环 1463* @param req 文件操作请求 1464* @param file 文件描述符 1465* @param bufs 要写入的数据 1466* @param nbufs 数据的数量 1467* @param off 文件的偏移量 1468* @param cb 完成后的回调函数 1469* 1470* @return 成功返回0,失败返回-1 1471*/ 1472int uv_fs_write(uv_loop_t* loop, 1473 uv_fs_t* req, 1474 uv_file file, 1475 const uv_buf_t bufs[], 1476 unsigned int nbufs, 1477 int64_t off, 1478 uv_fs_cb cb); 1479 1480/** 1481* @brief 异步复制文件 1482* 1483* @param loop 事件循环 1484* @param req 文件操作请求 1485* @param path 源文件路径 1486* @param new_path 目标文件路径 1487* @param flags 复制选项 1488* @param cb 完成后的回调函数 1489* 1490* @return 成功返回0,失败返回-1 1491*/ 1492int uv_fs_copyfile(uv_loop_t* loop, 1493 uv_fs_t* req, 1494 const char* path, 1495 const char* new_path 1496 int flags, 1497 uv_fs_cb cb); 1498``` 1499 1500- uv_getaddrinfo_t 1501 1502 函数原型: 1503 1504```cpp 1505/** 1506* @brief 异步获取地址信息 1507* 1508* @param loop 事件循环 1509* @param req 地址信息请求 1510* @param cb 完成后的回调函数 1511* @param hostname 主机名 1512* @param service 服务名 1513* @param hints 地址信息提示 1514* 1515* @return 成功返回0,失败返回-1 1516*/ 1517int uv_getaddrinfo(uv_loop_t* loop, 1518 uv_getaddrinfo_t* req, 1519 uv_getaddrinfo_cb cb, 1520 const char* hostname, 1521 const char* service, 1522 const struct addrinfo* hints); 1523``` 1524 1525- uv_getnameinfo_t 1526 1527 函数原型: 1528 1529```cpp 1530/** 1531* @brief 异步获取名称信息 1532* 1533* @param loop 事件循环 1534* @param req 名称信息请求 1535* @param getnameinfo_cb 完成后的回调函数 1536* @param addr 地址 1537* @param flags 标志 1538* 1539* @return 成功返回0,失败返回-1 1540*/ 1541int uv_getnameinfo(uv_loop_t* loop, 1542 uv_getnameinfo_t* req, 1543 uv_getnameinfo_cb getnameinfo_cb, 1544 const struct sockaddr* addr, 1545 int flags); 1546``` 1547 1548在应用主线程上不生效的接口主要包括: 1549 1550- idle句柄 1551- prepare句柄 1552- check句柄 1553- signal相关函数 1554- tcp及udp相关函数 1555 1556## 技术案例 1557 1558[libuv中主线程timer回调事件触发时间不正确原因](https://gitee.com/openharmony/third_party_libuv/wikis/06-Wiki-%E6%8A%80%E6%9C%AF%E8%B5%84%E6%BA%90/libuv%E4%B8%AD%E4%B8%BB%E7%BA%BF%E7%A8%8Btimer%E5%9B%9E%E8%B0%83%E4%BA%8B%E4%BB%B6%E8%A7%A6%E5%8F%91%E6%97%B6%E9%97%B4%E4%B8%8D%E6%AD%A3%E7%A1%AE%E5%8E%9F%E5%9B%A0) 1559 1560[libuv工作线程接入FFRT方案分析](https://gitee.com/openharmony/third_party_libuv/wikis/06-Wiki-%E6%8A%80%E6%9C%AF%E8%B5%84%E6%BA%90/%20libuv%E5%B7%A5%E4%BD%9C%E7%BA%BF%E7%A8%8B%E6%8E%A5%E5%85%A5FFRT%E6%96%B9%E6%A1%88%E5%88%86%E6%9E%90) 1561 1562[QoS感知的libuv、Node-API异步接口整改FAQ](https://gitee.com/openharmony/third_party_libuv/wikis/06-Wiki-%E6%8A%80%E6%9C%AF%E8%B5%84%E6%BA%90/QoS%E6%84%9F%E7%9F%A5%E7%9A%84libuv%E3%80%81napi%E5%BC%82%E6%AD%A5%E6%8E%A5%E5%8F%A3%E6%95%B4%E6%94%B9FAQ) 1563