Libuv库(探讨)---第二节:异步调度

系列目录:

libuv概设:https://blog.csdn.net/knowledgebao/article/details/82251307

libuv异步调度:https://blog.csdn.net/knowledgebao/article/details/82251513

libuv文件系统:https://blog.csdn.net/knowledgebao/article/details/82252619

libuv网络相关:https://blog.csdn.net/knowledgebao/article/details/82252653

libuv线程相关:https://blog.csdn.net/knowledgebao/article/details/82252664

libuv进程调度:https://blog.csdn.net/knowledgebao/article/details/82252683

libuv高级调度:https://blog.csdn.net/knowledgebao/article/details/82252698

libuv其它:https://blog.csdn.net/knowledgebao/article/details/82252716

异步调度逻辑

libuv是一个高性能事件驱动库,屏蔽了各种操作系统的差异从而提供了统一的APIlibuv严格使用异步、事件驱动的编程风格。其核心工作是提供事件循环及基于 I/O或其他活动事件的回调机制libuv库包含了诸如计时器、非阻塞网络支持、异步文件系统访问、线程创建、子进程等核心工具

libuv有二个主要功能,一个是循环调度模块,也就是异步IO的核心Loop模块,一个是全局的线程池Thread Pool,loop模块主要是用于异步通知,thread pool主要用于线程管理和调度。libuv库里边用到线程池的地方有DNS相关的二个函数getaddrinfogetnameinfo,文件各种异步操作。其他暂时不使用thread pool,thread pool主要是给调度者使用。

loop的调度一般通过二种模式调度,一种是Handle,一种是Request。Handle一般关联一个长期任务,request关联一个一次性任务。比如一个socket的监听任务,就是uv_udp_t(handle),而一次数据的发送就是一个请求任务,关联请求句柄uv_udp_send_t(request)。

handle和req每一个变量里边都关联了一个 void* data,用于关联上下文,下图是req和handle的分类。所有的req类型都有一个UV_REQ_FIELDS结构,所以都可以强转为uf_req_t。所有的handle都有一个UV_HANDLE_FIELDS结构,所以都可以强制转化为uv_handle_t.

下图是官网提供的架构图,TCP、UDP、TTY、PIPE等依赖系统的uv__io_t或IPCP异步机制实现异步IO的功能,FILE/DNS/用户代码依赖Thread Pool来实现异步IO机制。

libuv提供了一个线程池,可用于运行用户代码并在循环线程中得到通知。此线程池在内部用于运行所有文件系统操作,以及getaddrinfogetnameinfo请求。其默认大小为4,但可以通过将UV_THREADPOOL_SIZE环境变量设置为任何值(绝对最大值为128putenv("UV_THREADPOOL_SIZE=128"))在启动时更改 。线程池是全局的,并在所有事件循环中共享。当特定函数使用线程池时(即使用时),libuv会预分配并初始化允许的最大线程数。这会导致相对较小的内存开销(128个线程约为1MB),但会增加运行时的线程性能。

 

下图是loop的while循环流程,程序中可以有多个loop,每个loop实际上是一个while死循环,不停的检测各种事件,然后出发各种回调,TCP/UDP/TTY/PIPE等都是while死循环出发的回调,所以回调里边不要干时间太长的事。File I/O,DNS Ops,User cord是线程池出发的回调,可以适当干点长时间的事,具体loop循环,可以定位uv_run,跟进代码详细料及。

下边引用官网的话(http://docs.libuv.org/en/v1.x/design.html):

The I/O (or event) loop is the central part of libuv. It establishes the content for all I/O operations, and it’s meant to be tied to a single thread. One can run multiple event loops as long as each runs in a different thread. The libuv event loop (or any other API involving the loop or handles, for that matter) is not thread-safe except where stated otherwise.

The event loop follows the rather usual single threaded asynchronous I/O approach: all (network) I/O is performed on non-blocking sockets which are polled using the best mechanism available on the given platform: epoll on Linux, kqueue on OSX and other BSDs, event ports on SunOS and IOCP on Windows. As part of a loop iteration the loop will block waiting for I/O activity on sockets which have been added to the poller and callbacks will be fired indicating socket conditions (readable, writable hangup) so handles can read, write or perform the desired I/O operation.

In order to better understand how the event loop operates, the following diagram illustrates all stages of a loop iteration:

上图:

1The loop concept of ‘now’ is updated. The event loop caches the current time at the start of the event loop tick in order to reduce the number of time-related system calls.为了减少系统时间依赖性,循环开始位置更新当前时间指。

2If the loop is alive an iteration(循环) is started, otherwise the loop will exit immediately. So, when is a loop considered to be alive? If a loop has active and ref’d handles, active requests or closing handles it’s considered to be alive.loop存活的时候,

3Due timers are run. All active timers scheduled for a time before the loop’s concept of now get their callbacks called.

4Pending callbacks are called. All I/O callbacks are called right after polling for I/O, for the most part. There are cases, however, in which calling such a callback is deferred for the next loop iteration. If the previous iteration deferred any I/O callback it will be run at this point.

5Idle handle callbacks are called. Despite the unfortunate name, idle handles are run on every loop iteration, if they are active.

6Prepare handle callbacks are called. Prepare handles get their callbacks called right before the loop will block for I/O.

7Poll timeout is calculated. Before blocking for I/O the loop calculates for how long it should block. These are the rules when calculating the timeout:

  If the loop was run with the UV_RUN_NOWAIT flag, the timeout is 0.

  If the loop is going to be stopped (uv_stop() was called), the timeout is 0.

  If there are no active handles or requests, the timeout is 0.

  If there are any idle handles active, the timeout is 0.

  If there are any handles pending to be closed, the timeout is 0.

  If none of the above cases matches, the timeout of the closest timer is taken, or if there are no active timers, infinity.

8The loop blocks for I/O. At this point the loop will block for I/O for the duration calculated in the previous step. All I/O related handles that were monitoring a given file descriptor for a read or write operation get their callbacks called at this point.

9Check handle callbacks are called. Check handles get their callbacks called right after the loop has blocked for I/O. Check handles are essentially the counterpart of prepare handles.

10Close callbacks are called. If a handle was closed by calling uv_close() it will get the close callback called.

11Special case in case the loop was run with UV_RUN_ONCE, as it implies forward progress. It’s possible that no I/O callbacks were fired after blocking for I/O, but some time has passed so there might be timers which are due, those timers get their callbacks called.

12Iteration ends. If the loop was run with UV_RUN_NOWAIT or UV_RUN_ONCE modes the iteration ends and uv_run() will return. If the loop was run with UV_RUN_DEFAULT it will continue from the start if it’s still alive, otherwise it will also end.

Important

 libuv uses a thread pool to make asynchronous file I/O operations possible, but network I/O is always performed in a single thread, each loop’s thread.

Note

 While the polling mechanism is different, libuv makes the execution model consistent across Unix systems and Windows.

采用最小堆算法。

loop相关API

uv_loop_t* uv_default_loop(void);
int uv_loop_init(uv_loop_t* loop);
int uv_loop_close(uv_loop_t* loop);
uv_loop_t* uv_loop_new(void);
void uv_loop_delete(uv_loop_t*);
size_t uv_loop_size(void);
int uv_loop_alive(const uv_loop_t* loop);
int uv_loop_configure(uv_loop_t* loop, uv_loop_option option, ...);
int uv_loop_fork(uv_loop_t* loop);

http://docs.libuv.org/en/v1.x/loop.html

uv_default_loop获取默认的队列

uv_loop_init队列初始化,参数loop必须allocate

uv_loop_close

Releases all internal loop resources. Call this function only when the loop has finished executing and all open handles and requests have been closed, or it will return UV_EBUSY. After this function returns, the user can free the memory allocated for the loop.

关闭队列,执行此函数的前提是所有任务必须都执行完成,如果有任务遗留,会返回UV_EBUSY.

uv_loop_newuv_loop_deletemalloc+uv_loop_inituv_loop_close+free,不推荐使用

uv_loop_size

uv_loop_alive

Returns non-zero if there are referenced active handles, active requests or closing handles in the loop.

Loop里边有任务(不管uv_run是否启动),返回非0,否则返回0。一般uv_run运行返回后以及添加任务区,返回0.

uv_loop_configure

Set additional loop options. You should normally call this before the first call to uv_run() unless mentioned otherwise.

Returns 0 on success or a UV_E* error code on failure. Be prepared to handle UV_ENOSYS; it means the loop option is not supported by the platform.

Supported options:

UV_LOOP_BLOCK_SIGNAL: Block a signal when polling for new events. The second argument to uv_loop_configure() is the signal number.

This operation is currently only implemented for SIGPROF signals, to suppress unnecessary wakeups when using a sampling profiler. Requesting other signals will fail with UV_EINVAL.

设置loop的属性,此接口在uv_loop之前调用。目前option仅支持SIGPROF,阻塞指定信号。

#ifdef _WIN32

ASSERT(UV_ENOSYS == uv_loop_configure(&loop, UV_LOOP_BLOCK_SIGNAL, 0));

#else

ASSERT(0 == uv_loop_configure(&loop, UV_LOOP_BLOCK_SIGNAL, SIGPROF));

#endif

uv_loop_fork复制loop

loop运行

int uv_run(uv_loop_t*, uv_run_mode mode);
void uv_stop(uv_loop_t*);

void uv_update_time(uv_loop_t*);
uint64_t uv_now(const uv_loop_t*);

int uv_backend_fd(const uv_loop_t*);
int uv_backend_timeout(const uv_loop_t*);

参考网址:

Libuv源码分析:https://www.cnblogs.com/watercoldyi/p/5675180.html

IO模型及select、poll、epoll和kqueue的区别https://www.cnblogs.com/linganxiong/p/5583415.html

Reactor模式--VS--Proactor模式 :https://blog.csdn.net/wenbingoon/article/details/9880365

uv_run:

运行loop

This function runs the event loop. It will act differently depending on the specified mode:

UV_RUN_DEFAULT: Runs the event loop until there are no more active and referenced handles or requests. Returns non-zero if uv_stop() was called and there are still active handles or requests. Returns zero in all other cases.阻塞运行到直到手动停止或没有被任何请求引用。

UV_RUN_ONCE: Poll for i/o once. Note that this function blocks if there are no pending callbacks. Returns zero when done (no active handles or requests left), or non-zero if more callbacks are expected (meaning you should run the event loop again sometime in the future). UV_ONCE,阻塞模式运行,直到处理了一次请求。

UV_RUN_NOWAIT: Poll for i/o once but don’t block if there are no pending callbacks. Returns zero if done (no active handles or requests left), or non-zero if more callbacks are expected (meaning you should run the event loop again sometime in the future).运行一次循环流程

uv_stop:

uv_stop() can be used to stop an event loop. The earliest the loop will stop running is on the next iteration, possibly later. This means that events that are ready to be processed in this iteration of the loop will still be processed, so uv_stop() can’t be used as a kill switch. When uv_stop() is called, the loop won’t block for i/o on this iteration. The semantics of these things can be a bit difficult to understand, so let’s look at uv_run() where all the control flow occurs.

停止uv_run运行,停止运行后,loop里的任务还是有效的,只是不在执行,再次调用uv_run还可以继续运行。

uv_now

Return the current timestamp in milliseconds. The timestamp is cached at the start of the event loop tick, see uv_update_time() for details and rationale.

NoteUse uv_hrtime() if you need sub-millisecond granularity.

获取loop时间loop->time,单位毫秒,如果需要微妙进度,请使用uv_hrtime().loopuv_run过程中,此值才会不断更新。

uv_update_time

Update the event loop’s concept of “now”. Libuv caches the current time at the start of the event loop tick in order to reduce the number of time-related system calls.

You won’t normally need to call this function unless you have callbacks that block the event loop for longer periods of time, where “longer” is somewhat subjective but probably on the order of a millisecond or more.

立刻刷新loop的当前时间loop->time,目的是使uv_now更精确

uv_backend_fd:

Get backend file descriptor. Only kqueue, epoll and event ports(端口事件) are supported.

This can be used in conjunction with uv_run(loop, UV_RUN_NOWAIT) to poll in one thread and run the event loop’s callbacks in another see test/test-embed.c for an example.

Note:Embedding a kqueue fd in another kqueue pollset doesn’t work on all platforms. It’s not an error to add the fd but it never generates events.

uv_backend_timeout:

Get the poll timeout. The return value is in milliseconds, or -1 for no timeout.

下一个Loop到来时间。单位毫秒。

handle相关:

int uv_is_active(const uv_handle_t* handle);
int uv_is_closing(const uv_handle_t* handle);
void uv_close(uv_handle_t* handle, uv_close_cb close_cb);

void uv_ref(uv_handle_t*);
void uv_unref(uv_handle_t*);
int uv_has_ref(const uv_handle_t*);

size_t uv_handle_size(uv_handle_type type);
uv_handle_type uv_handle_get_type(const uv_handle_t* handle);
const char* uv_handle_type_name(uv_handle_type type);

uv_loop_t* uv_handle_get_loop(const uv_handle_t* handle);
void* uv_handle_get_data(const uv_handle_t* handle);
void uv_handle_set_data(uv_handle_t* handle, void* data);

int uv_send_buffer_size(uv_handle_t* handle, int* value);
int uv_recv_buffer_size(uv_handle_t* handle, int* value);

int uv_fileno(const uv_handle_t* handle, uv_os_fd_t* fd);

http://docs.libuv.org/en/v1.x/handle.html

The libuv event loop (if run in the default mode) will run until there are no active and referenced handles left. The user can force the loop to exit early by unreferencing handles which are active, for example by calling uv_unref() after calling uv_timer_start().

A handle can be referenced or unreferenced, the refcounting scheme doesn’t use a counter, so both operations are idempotent.

All handles are referenced when active by default, see uv_is_active() for a more detailed explanation on what being activeinvolves.

 

uv_is_active:

Returns non-zero if the handle is active, zero if it’s inactive. What “active” means depends on the type of handle:

A uv_async_t handle is always active and cannot be deactivated, except by closing it with uv_close().

A uv_pipe_t, uv_tcp_t, uv_udp_t, etc. handle - basically any handle that deals with i/o - is active when it is doing something that involves i/o, like reading, writing, connecting, accepting new connections, etc.

A uv_check_t, uv_idle_t, uv_timer_t, etc. handle is active when it has been started with a call to uv_check_start(), uv_idle_start(), etc.

Rule of thumb: if a handle of type uv_foo_t has a uv_foo_start() function, then it’s active from the moment that function is called. Likewise, uv_foo_stop() deactivates the handle again.

请求是否处于活动状态,执行uv_*start后,就处于active状态,uv_*stop后,处于非active状态,此状态不可修改。

uv_is_closing: Returns non-zero if the handle is closing or closed, zero otherwise.

Note: This function should only be used between the initialization of the handle and the arrival of the close callback.

此接口仅在创建于结束之间有效,如果结束后,结果无实际意义.

uv_close:endgame队列中删除

Request handle to be closed. close_cb will be called asynchronously after this call. This MUST be called on each handle before memory is released.

Handles that wrap file descriptors are closed immediately but close_cb will still be deferred to the next iteration of the event loop. It gives you a chance to free up any resources associated with the handle.

In-progress requests, like uv_connect_t or uv_write_t, are cancelled and have their callbacks called asynchronously with status=UV_ECANCELED.

关闭指定句柄,回调可以为空(同步返回),比如关闭定时器、idleprepare等,一般和各种init成对出现。

uv_ref:

Reference the given handle. References are idempotent, that is, if a handle is already referenced calling this function again will have no effect.

指定句柄为ref状态,默认handle执行Start之后,默认含有此状态。

uv_unref:

Un-reference the given handle. References are idempotent, that is, if a handle is not referenced calling this function again will have no effect.

取消引用,多次无效(标志设置),可以触发loop退出。

uv_has_ref:

Returns non-zero if the handle referenced, zero otherwise.

uv_handle_get_type:

Returns handle->type

获取handle类型

uv_handle_size:

Returns the size of the given handle type. Useful for FFI binding writers who don’t want to know the structure layout.

获取handle类型的编号

uv_handle_type_name:

Returns the name for the equivalent struct for a given handle type, e.g. “pipe” (as in uv_pipe_t) for UV_NAMED_PIPE.

If no such handle type exists, this returns NULL.

获取类型描述名称

uv_handle_get_loop

Returns handle->loop.

uv_handle_get_data

Returns handle->data.

uv_handle_set_data

Sets handle->data to data.

uv_send_buffer_size

Gets or sets the size of the send buffer that the operating system uses for the socket.

If *value == 0, it will return the current send buffer size, otherwise it will use *value to set the new send buffer size.

This function works for TCP, pipe and UDP handles on Unix and for TCP and UDP handles on Windows.

NoteLinux will set double the size and return double the size of the original set value.

设置/获取,发送缓存大小,注意:linux会设置二倍大小

uv_recv_buffer_size

Gets or sets the size of the receive buffer that the operating system uses for the socket.

If *value == 0, it will return the current receive buffer size, otherwise it will use *value to set the new receive buffer size.

This function works for TCP, pipe and UDP handles on Unix and for TCP and UDP handles on Windows.

NoteLinux will set double the size and return double the size of the original set value.

uv_fileno

Gets the platform dependent file descriptor equivalent.

The following handles are supported: TCP, pipes, TTY, UDP and poll. Passing any other handle type will fail with UV_EINVAL.

If a handle doesn’t have an attached file descriptor yet or the handle itself has been closed, this function will return UV_EBADF.

WarningBe very careful when using this function. libuv assumes it’s in control of the file descriptor so any change to it may lead to malfunction.

提取句柄。比如:handle->io_watcher.fd;

线程通信

int uv_async_init(uv_loop_t*,
                            uv_async_t* async,
                            uv_async_cb async_cb);
int uv_async_send(uv_async_t* async);

http://docs.libuv.org/en/v1.x/async.html

线程之间通信。

uv_async_initInitialize the handle. A NULL callback is allowed.

Returns:0 on success, or an error code < 0 on failure.Note

Unlike other handle initialization functions, it immediately starts the handle.

初始化async句柄。

uv_async_send

Wake up the event loop and call the async handle’s callback.

Returns:0 on success, or an error code < 0 on failure.Note

It’s safe to call this function from any thread. The callback will be called on the loop thread.

Warninglibuv will coalesce calls to uv_async_send(), that is, not every call to it will yield an execution of the callback. For example: if uv_async_send() is called 5 times in a row before the callback is called, the callback will only be called once. If uv_async_send() is called again after the callback was called, it will be called again.

通知async句柄,此函数是线程安全的,但是多次发送uv_async_send有可能只触发一次uv_async_cb

异步消息

int uv_idle_init(uv_loop_t*, uv_idle_t* idle);
int uv_idle_start(uv_idle_t* idle, uv_idle_cb cb);
int uv_idle_stop(uv_idle_t* idle);

int uv_prepare_init(uv_loop_t*, uv_prepare_t* prepare);
int uv_prepare_start(uv_prepare_t* prepare, uv_prepare_cb cb);
int uv_prepare_stop(uv_prepare_t* prepare);

int uv_check_init(uv_loop_t*, uv_check_t* check);
int uv_check_start(uv_check_t* check, uv_check_cb cb);
int uv_check_stop(uv_check_t* check);

http://docs.libuv.org/en/v1.x/prepare.html

https://www.cnblogs.com/watercoldyi/p/5682344.html

uv__handle_init() -> uv__handle_start() -> uv__handle_stop() - > uv__handle_closing() -> uv_want_endgame()-> uv__handle_close()

1,  init,初始化,返回句柄。

2start,添加句柄到执行队列,准备执行,设置回调,如果handle已经存在,添加无效。不管是否有效,都返回0

3stop,从执行队列移到endgame队列

线程池调用

int uv_queue_work(uv_loop_t* loop,
                            uv_work_t* req,
                            uv_work_cb work_cb,
                            uv_after_work_cb after_work_cb);

int uv_cancel(uv_req_t* req);

 

http://docs.libuv.org/en/v1.x/guide/threads.html

libuv provides a threadpool which can be used to run user code and get notified in the loop thread. This thread pool is internally used to run all file system operations, as well as getaddrinfo and getnameinfo requests.

Its default size is 4, but it can be changed at startup time by setting the UV_THREADPOOL_SIZE environment variable to any value (the absolute maximum is 128).

The threadpool is global and shared across all event loops. When a particular function makes use of the threadpool (i.e. when using uv_queue_work()) libuv preallocates and initializes the maximum number of threads allowed by UV_THREADPOOL_SIZE. This causes a relatively minor memory overhead (~1MB for 128 threads) but increases the performance of threading at runtime.

libuv提供了一个线程池,可用于运行用户代码并在循环线程中得到通知。此线程池在内部用于运行所有文件系统操作,以及getaddrinfogetnameinfo请求。

其默认大小为4,但可以通过将UV_THREADPOOL_SIZE环境变量设置为任何值(绝对最大值为128putenv("UV_THREADPOOL_SIZE=128"))在启动时更改 。

线程池是全局的,并在所有事件循环中共享。当特定函数使用线程池时(即使用时),libuv会预分配并初始化允许的最大线程数。这会导致相对较小的内存开销(128个线程约为1MB),但会增加运行时的线程性能。

uv_queue_work

Initializes a work request which will run the given work_cb in a thread from the threadpool. Once work_cb is completed, after_work_cb will be called on the loop thread.

This request can be cancelled with uv_cancel().

从线程池中获取一个线程,执行work_cb任务。执行完成后,通过after_work_cb通知。work_cb不可以为空,否则after_work_cb不会执行。

Loop主要用来调用after_work_cb回调结果,如果想要after_work_cb返回,loop必须调用uv_run.

从线程池获取线程,然后执行对应的任务,无需自己创建线程。

uv_cancel:取请求执行

注意事项:

创建子进程用spawn,不要forkfork的子进程中调用uv_queue_work,不执行

 

猜你喜欢

转载自blog.csdn.net/knowledgebao/article/details/82251513