• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
138index.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```
223index.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
337index.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```
459index.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
792index.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
1045index.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
1173index.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![线程间通信原理](./figures/libuv-image-1.jpg)
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![libuv线程池工作原理](./figures/libuv-image-3.jpg)
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![libuv的异步线程池在OpenHarmony中的应用现状](./figures/libuv-ffrt.jpg)
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