|
160
|
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
|