How does the UV_RUN_NOWAIT mode work in libuv?

2019-03-27 15:09发布

问题:

When running an event loop in libuv using the uv_run function, there's a "mode" parameter that is used with the following values:

UV_RUN_DEFAULT
UV_RUN_ONCE
UV_RUN_NOWAIT

The first two are obvious. UV_RUN_DEFAULT runs the event loop until there are no more events, and UV_RUN_ONCE processing a single event from the loop. However, UV_RUN_NOWAIT doesn't seem to be a separate mode, but rather a flag that can be ORed with one of the other two values.

By default, this function blocks until events are done processing, and UV_RUN_NOWAIT makes it nonblocking, but any documentation I can find on it ends there. My question is, if you run the event loop nonblocking, how are callbacks handled?

The libuv event model is single-threaded (reactor pattern), so I'd assume it needs to block to be able to call the callbacks, but if the main thread is occupied, what happens to an event after it's processed? Will the callback be "queued" until libuv gets control of the main thread again? Or will the callbacks be dispatched on another thread?

回答1:

Callbacks are handled in the same manner. They will run within the thread that is in uv_run().

Per the documentation:

  • UV_RUN_DEFAULT: Runs the event loop until the reference count drops to zero. Always returns zero.
  • UV_RUN_ONCE: Poll for new events once. Note that this function blocks if there are no pending events. Returns zero when done (no active handles or requests left), or non-zero if more events are expected (meaning you should run the event loop again sometime in the future).
  • UV_RUN_NOWAIT: Poll for new events once but don't block if there are no pending events.

Consider the case where a program has a single watcher listening to a socket. In this scenario, an event would be created when the socket has received data.

  • UV_RUN_DEFAULT will block the caller even if the socket does not have data. The caller will return from uv_run(), when either:
    • The loop has been explicitly stopped, via uv_stop()
    • No more watchers are running in the loop. For example, the only watcher has been stopped.
  • UV_RUN_ONCE will block the caller even if the socket does not have data. The caller will return from uv_run(), when any of the following occur:
    • The loop has been explicitly stopped, via uv_stop()
    • No more watchers are running in the loop. For example, the only watcher has been stopped.
    • It has handled a max of one event. For example, the socket received data, and the user callback has been invoked. Additional events may be ready to be handled, but will not be handled in the current uv_run() call.
  • UV_RUN_NOWAIT will return if the socket does not have data.

Often times, running an event-loop in a non-blocking manner is done to integrate with other event-loops. Consider an application that has two event loops: libuv for backend work and Qt UI (which is driven by its own event loop). Being able to run the event loop in a non-blocking manner allows for a single thread to dispatch events on both event-loops. Here is a simplistic overview showing two libuv loops being handled by a single thread:

uv_loop_t *loop1 = uv_loop_new();
uv_loop_t *loop2 = uv_loop_new();

// create, initialize, and start a watcher for each loop.
...

// Handle two event loops with a single thread.
while (uv_run(loop1, UV_RUN_NOWAIT) || uv_run(loop2, UV_RUN_NOWAIT));

Without using UV_RUN_NOWAIT, loop2 would only run once loop1 or loop1's watchers have been stopped.

For more information, consider reading the Advanced Event Loops and Processes sections of An Introduction to libuv.