comparison third_party/libuv/docs/src/guide/threads.rst @ 160:948de3f54cea

[ThirdParty] Added libuv
author June Park <parkjune1995@gmail.com>
date Wed, 14 Jan 2026 19:39:52 -0800
parents
children
comparison
equal deleted inserted replaced
159:05cf9467a1c3 160:948de3f54cea
1 Threads
2 =======
3
4 Wait a minute? Why are we on threads? Aren't event loops supposed to be **the
5 way** to do *web-scale programming*? Well... no. Threads are still the medium in
6 which processors do their jobs. Threads are therefore mighty useful sometimes, even
7 though you might have to wade through various synchronization primitives.
8
9 Threads are used internally to fake the asynchronous nature of all of the system
10 calls. libuv also uses threads to allow you, the application, to perform a task
11 asynchronously that is actually blocking, by spawning a thread and collecting
12 the result when it is done.
13
14 Today there are two predominant thread libraries: the Windows threads
15 implementation and POSIX's :man:`pthreads(7)`. libuv's thread API is analogous to
16 the pthreads API and often has similar semantics.
17
18 A notable aspect of libuv's thread facilities is that it is a self contained
19 section within libuv. Whereas other features intimately depend on the event
20 loop and callback principles, threads are complete agnostic, they block as
21 required, signal errors directly via return values, and, as shown in the
22 :ref:`first example <thread-create-example>`, don't even require a running
23 event loop.
24
25 libuv's thread API is also very limited since the semantics and syntax of
26 threads are different on all platforms, with different levels of completeness.
27
28 This chapter makes the following assumption: **There is only one event loop,
29 running in one thread (the main thread)**. No other thread interacts
30 with the event loop (except using ``uv_async_send``).
31
32 Core thread operations
33 ----------------------
34
35 There isn't much here, you just start a thread using ``uv_thread_create()`` and
36 wait for it to close using ``uv_thread_join()``.
37
38 .. _thread-create-example:
39
40 .. rubric:: thread-create/main.c
41 .. literalinclude:: ../../code/thread-create/main.c
42 :language: c
43 :linenos:
44 :lines: 26-36
45 :emphasize-lines: 3-7
46
47 .. tip::
48
49 ``uv_thread_t`` is just an alias for ``pthread_t`` on Unix, but this is an
50 implementation detail, avoid depending on it to always be true.
51
52 The second parameter is the function which will serve as the entry point for
53 the thread, the last parameter is a ``void *`` argument which can be used to pass
54 custom parameters to the thread. The function ``hare`` will now run in a separate
55 thread, scheduled pre-emptively by the operating system:
56
57 .. rubric:: thread-create/main.c
58 .. literalinclude:: ../../code/thread-create/main.c
59 :language: c
60 :linenos:
61 :lines: 6-14
62 :emphasize-lines: 2
63
64 Unlike ``pthread_join()`` which allows the target thread to pass back a value to
65 the calling thread using a second parameter, ``uv_thread_join()`` does not. To
66 send values use :ref:`inter-thread-communication`.
67
68 Synchronization Primitives
69 --------------------------
70
71 This section is purposely spartan. This book is not about threads, so I only
72 catalogue any surprises in the libuv APIs here. For the rest you can look at
73 the :man:`pthreads(7)` man pages.
74
75 Mutexes
76 ~~~~~~~
77
78 The mutex functions are a **direct** map to the pthread equivalents.
79
80 .. rubric:: libuv mutex functions
81 .. code-block:: c
82
83 int uv_mutex_init(uv_mutex_t* handle);
84 int uv_mutex_init_recursive(uv_mutex_t* handle);
85 void uv_mutex_destroy(uv_mutex_t* handle);
86 void uv_mutex_lock(uv_mutex_t* handle);
87 int uv_mutex_trylock(uv_mutex_t* handle);
88 void uv_mutex_unlock(uv_mutex_t* handle);
89
90 The ``uv_mutex_init()``, ``uv_mutex_init_recursive()`` and ``uv_mutex_trylock()``
91 functions will return 0 on success, and an error code otherwise.
92
93 If `libuv` has been compiled with debugging enabled, ``uv_mutex_destroy()``,
94 ``uv_mutex_lock()`` and ``uv_mutex_unlock()`` will ``abort()`` on error.
95 Similarly ``uv_mutex_trylock()`` will abort if the error is anything *other
96 than* ``EAGAIN`` or ``EBUSY``.
97
98 Recursive mutexes are supported, but you should not rely on them. Also, they
99 should not be used with ``uv_cond_t`` variables.
100
101 The default BSD mutex implementation will raise an error if a thread which has
102 locked a mutex attempts to lock it again. For example, a construct like::
103
104 uv_mutex_init(a_mutex);
105 uv_mutex_lock(a_mutex);
106 uv_thread_create(thread_id, entry, (void *)a_mutex);
107 uv_mutex_lock(a_mutex);
108 // more things here
109
110 can be used to wait until another thread initializes some stuff and then
111 unlocks ``a_mutex`` but will lead to your program crashing if in debug mode, or
112 return an error in the second call to ``uv_mutex_lock()``.
113
114 .. note::
115
116 Mutexes on Windows are always recursive.
117
118 Locks
119 ~~~~~
120
121 Read-write locks are a more granular access mechanism. Two readers can access
122 shared memory at the same time. A writer may not acquire the lock when it is
123 held by a reader. A reader or writer may not acquire a lock when a writer is
124 holding it. Read-write locks are frequently used in databases. Here is a toy
125 example.
126
127 .. rubric:: locks/main.c - simple rwlocks
128 .. literalinclude:: ../../code/locks/main.c
129 :language: c
130 :linenos:
131 :emphasize-lines: 13,16,27,31,42,55
132
133 Run this and observe how the readers will sometimes overlap. In case of
134 multiple writers, schedulers will usually give them higher priority, so if you
135 add two writers, you'll see that both writers tend to finish first before the
136 readers get a chance again.
137
138 We also use barriers in the above example so that the main thread can wait for
139 all readers and writers to indicate they have ended.
140
141 Others
142 ~~~~~~
143
144 libuv also supports semaphores_, `condition variables`_ and barriers_ with APIs
145 very similar to their pthread counterparts.
146
147 .. _semaphores: https://en.wikipedia.org/wiki/Semaphore_(programming)
148 .. _condition variables: https://en.wikipedia.org/wiki/Monitor_(synchronization)#Condition_variables_2
149 .. _barriers: https://en.wikipedia.org/wiki/Barrier_(computer_science)
150
151 In addition, libuv provides a convenience function ``uv_once()``. Multiple
152 threads can attempt to call ``uv_once()`` with a given guard and a function
153 pointer, **only the first one will win, the function will be called once and
154 only once**::
155
156 /* Initialize guard */
157 static uv_once_t once_only = UV_ONCE_INIT;
158
159 int i = 0;
160
161 void increment() {
162 i++;
163 }
164
165 void thread1() {
166 /* ... work */
167 uv_once(once_only, increment);
168 }
169
170 void thread2() {
171 /* ... work */
172 uv_once(once_only, increment);
173 }
174
175 int main() {
176 /* ... spawn threads */
177 }
178
179 After all threads are done, ``i == 1``.
180
181 .. _libuv-work-queue:
182
183 libuv v0.11.11 onwards also added a ``uv_key_t`` struct and api_ for
184 thread-local storage.
185
186 .. _api: http://docs.libuv.org/en/v1.x/threading.html#thread-local-storage
187
188 libuv work queue
189 ----------------
190
191 ``uv_queue_work()`` is a convenience function that allows an application to run
192 a task in a separate thread, and have a callback that is triggered when the
193 task is done. A seemingly simple function, what makes ``uv_queue_work()``
194 tempting is that it allows potentially any third-party libraries to be used
195 with the event-loop paradigm. When you use event loops, it is *imperative to
196 make sure that no function which runs periodically in the loop thread blocks
197 when performing I/O or is a serious CPU hog*, because this means that the loop
198 slows down and events are not being handled at full capacity.
199
200 However, a lot of existing code out there features blocking functions (for example
201 a routine which performs I/O under the hood) to be used with threads if you
202 want responsiveness (the classic 'one thread per client' server model), and
203 getting them to play with an event loop library generally involves rolling your
204 own system of running the task in a separate thread. libuv just provides
205 a convenient abstraction for this.
206
207 Here is a simple example inspired by `node.js is cancer`_. We are going to
208 calculate fibonacci numbers, sleeping a bit along the way, but run it in
209 a separate thread so that the blocking and CPU bound task does not prevent the
210 event loop from performing other activities.
211
212 .. rubric:: queue-work/main.c - lazy fibonacci
213 .. literalinclude:: ../../code/queue-work/main.c
214 :language: c
215 :linenos:
216 :lines: 17-29
217
218 The actual task function is simple, nothing to show that it is going to be
219 run in a separate thread. The ``uv_work_t`` structure is the clue. You can pass
220 arbitrary data through it using the ``void* data`` field and use it to
221 communicate to and from the thread. But be sure you are using proper locks if
222 you are changing things while both threads may be running.
223
224 The trigger is ``uv_queue_work``:
225
226 .. rubric:: queue-work/main.c
227 .. literalinclude:: ../../code/queue-work/main.c
228 :language: c
229 :linenos:
230 :lines: 31-44
231 :emphasize-lines: 10
232
233 The thread function will be launched in a separate thread, passed the
234 ``uv_work_t`` structure and once the function returns, the *after* function
235 will be called on the thread the event loop is running in. It will be passed
236 the same structure.
237
238 For writing wrappers to blocking libraries, a common :ref:`pattern <baton>`
239 is to use a baton to exchange data.
240
241 Since libuv version `0.9.4` an additional function, ``uv_cancel()``, is
242 available. This allows you to cancel tasks on the libuv work queue. Only tasks
243 that *are yet to be started* can be cancelled. If a task has *already started
244 executing, or it has finished executing*, ``uv_cancel()`` **will fail**.
245
246 ``uv_cancel()`` is useful to cleanup pending tasks if the user requests
247 termination. For example, a music player may queue up multiple directories to
248 be scanned for audio files. If the user terminates the program, it should quit
249 quickly and not wait until all pending requests are run.
250
251 Let's modify the fibonacci example to demonstrate ``uv_cancel()``. We first set
252 up a signal handler for termination.
253
254 .. rubric:: queue-cancel/main.c
255 .. literalinclude:: ../../code/queue-cancel/main.c
256 :language: c
257 :linenos:
258 :lines: 43-
259
260 When the user triggers the signal by pressing ``Ctrl+C`` we send
261 ``uv_cancel()`` to all the workers. ``uv_cancel()`` will return ``0`` for those that are already executing or finished.
262
263 .. rubric:: queue-cancel/main.c
264 .. literalinclude:: ../../code/queue-cancel/main.c
265 :language: c
266 :linenos:
267 :lines: 33-41
268 :emphasize-lines: 6
269
270 For tasks that do get cancelled successfully, the *after* function is called
271 with ``status`` set to ``UV_ECANCELED``.
272
273 .. rubric:: queue-cancel/main.c
274 .. literalinclude:: ../../code/queue-cancel/main.c
275 :language: c
276 :linenos:
277 :lines: 28-31
278 :emphasize-lines: 2
279
280 ``uv_cancel()`` can also be used with ``uv_fs_t`` and ``uv_getaddrinfo_t``
281 requests. For the filesystem family of functions, ``uv_fs_t.errorno`` will be
282 set to ``UV_ECANCELED``.
283
284 .. TIP::
285
286 A well designed program would have a way to terminate long running workers
287 that have already started executing. Such a worker could periodically check
288 for a variable that only the main process sets to signal termination.
289
290 .. _inter-thread-communication:
291
292 Inter-thread communication
293 --------------------------
294
295 Sometimes you want various threads to actually send each other messages *while*
296 they are running. For example you might be running some long duration task in
297 a separate thread (perhaps using ``uv_queue_work``) but want to notify progress
298 to the main thread. This is a simple example of having a download manager
299 informing the user of the status of running downloads.
300
301 .. rubric:: progress/main.c
302 .. literalinclude:: ../../code/progress/main.c
303 :language: c
304 :linenos:
305 :lines: 7-8,35-
306 :emphasize-lines: 2,11
307
308 The async thread communication works *on loops* so although any thread can be
309 the message sender, only threads with libuv loops can be receivers (or rather
310 the loop is the receiver). libuv will invoke the callback (``print_progress``)
311 with the async watcher whenever it receives a message.
312
313 .. warning::
314
315 It is important to realize that since the message send is *async*, the callback
316 may be invoked immediately after ``uv_async_send`` is called in another
317 thread, or it may be invoked after some time. libuv may also combine
318 multiple calls to ``uv_async_send`` and invoke your callback only once. The
319 only guarantee that libuv makes is -- The callback function is called *at
320 least once* after the call to ``uv_async_send``. If you have no pending
321 calls to ``uv_async_send``, the callback won't be called. If you make two
322 or more calls, and libuv hasn't had a chance to run the callback yet, it
323 *may* invoke your callback *only once* for the multiple invocations of
324 ``uv_async_send``. Your callback will never be called twice for just one
325 event.
326
327 .. rubric:: progress/main.c
328 .. literalinclude:: ../../code/progress/main.c
329 :language: c
330 :linenos:
331 :lines: 10-24
332 :emphasize-lines: 7-8
333
334 In the download function, we modify the progress indicator and queue the message
335 for delivery with ``uv_async_send``. Remember: ``uv_async_send`` is also
336 non-blocking and will return immediately.
337
338 .. rubric:: progress/main.c
339 .. literalinclude:: ../../code/progress/main.c
340 :language: c
341 :linenos:
342 :lines: 31-34
343
344 The callback is a standard libuv pattern, extracting the data from the watcher.
345
346 Finally it is important to remember to clean up the watcher.
347
348 .. rubric:: progress/main.c
349 .. literalinclude:: ../../code/progress/main.c
350 :language: c
351 :linenos:
352 :lines: 26-29
353 :emphasize-lines: 3
354
355 After this example, which showed the abuse of the ``data`` field, bnoordhuis_
356 pointed out that using the ``data`` field is not thread safe, and
357 ``uv_async_send()`` is actually only meant to wake up the event loop. Use
358 a mutex or rwlock to ensure accesses are performed in the right order.
359
360 .. note::
361
362 mutexes and rwlocks **DO NOT** work inside a signal handler, whereas
363 ``uv_async_send`` does.
364
365 One use case where ``uv_async_send`` is required is when interoperating with
366 libraries that require thread affinity for their functionality. For example in
367 node.js, a v8 engine instance, contexts and its objects are bound to the thread
368 that the v8 instance was started in. Interacting with v8 data structures from
369 another thread can lead to undefined results. Now consider some node.js module
370 which binds a third party library. It may go something like this:
371
372 1. In node, the third party library is set up with a JavaScript callback to be
373 invoked for more information::
374
375 var lib = require('lib');
376 lib.on_progress(function() {
377 console.log("Progress");
378 });
379
380 lib.do();
381
382 // do other stuff
383
384 2. ``lib.do`` is supposed to be non-blocking but the third party lib is
385 blocking, so the binding uses ``uv_queue_work``.
386
387 3. The actual work being done in a separate thread wants to invoke the progress
388 callback, but cannot directly call into v8 to interact with JavaScript. So
389 it uses ``uv_async_send``.
390
391 4. The async callback, invoked in the main loop thread, which is the v8 thread,
392 then interacts with v8 to invoke the JavaScript callback.
393
394 ----
395
396 .. _node.js is cancer: http://widgetsandshit.com/teddziuba/2011/10/node-js-is-cancer.html
397 .. _bnoordhuis: https://github.com/bnoordhuis