[gnome-devel-docs] programming-guidelines: Add a page on threading and async operations
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-devel-docs] programming-guidelines: Add a page on threading and async operations
- Date: Wed, 11 Feb 2015 10:23:26 +0000 (UTC)
commit 7062acdc6123b95bee80ed0a208866ea7605cdea
Author: Philip Withnall <philip withnall collabora co uk>
Date: Mon Feb 9 16:47:10 2015 +0000
programming-guidelines: Add a page on threading and async operations
https://bugzilla.gnome.org/show_bug.cgi?id=376123
programming-guidelines/C/threading.page | 325 +++++++++++++++++++++++++++++++
programming-guidelines/Makefile.am | 1 +
2 files changed, 326 insertions(+), 0 deletions(-)
---
diff --git a/programming-guidelines/C/threading.page b/programming-guidelines/C/threading.page
new file mode 100644
index 0000000..36ee7bb
--- /dev/null
+++ b/programming-guidelines/C/threading.page
@@ -0,0 +1,325 @@
+<page xmlns="http://projectmallard.org/1.0/"
+ xmlns:its="http://www.w3.org/2005/11/its"
+ xmlns:xi="http://www.w3.org/2003/XInclude"
+ type="topic"
+ id="threading">
+
+ <info>
+ <link type="guide" xref="index#coding-style"/>
+
+ <credit type="author copyright">
+ <name>Philip Withnall</name>
+ <email its:translate="no">philip withnall collabora co uk</email>
+ <years>2015</years>
+ </credit>
+
+ <include href="cc-by-sa-3-0.xml" xmlns="http://www.w3.org/2001/XInclude"/>
+
+ <desc>Moving computation out of the main thread into worker threads</desc>
+ </info>
+
+ <title>Threading</title>
+
+ <synopsis>
+ <title>Summary</title>
+
+ <comment>
+ <p>
+ FIXME: Incorporate
+ https://tecnocode.co.uk/2014/03/27/what-is-gmaincontext/ and
+ https://tecnocode.co.uk/2014/04/19/ensuring-functions-are-called-in-the-right-context/
+ into the page.
+ </p>
+ </comment>
+
+ <list>
+ <item><p>
+ Do not use threads if at all possible.
+ (<link xref="#when-to-use-threading"/>)
+ </p></item>
+ <item><p>
+ If threads have to be used, use <code>GTask</code> or
+ <code>GThreadPool</code> and isolate the threaded code as much as
+ possible.
+ (<link xref="#using-threading"/>)
+ </p></item>
+ <item><p>
+ Use <code>g_thread_join()</code> to avoid leaking thread resources if
+ using <code>GThread</code> manually.
+ (<link xref="#using-threading"/>)
+ </p></item>
+ <item><p>
+ Be careful about the <code>GMainContext</code> which code is executed in
+ if using threads. Executing code in the wrong context can cause race
+ conditions, or block the main loop.
+ (<link xref="#using-threading"/>)
+ </p></item>
+ </list>
+ </synopsis>
+
+ <section id="when-to-use-threading">
+ <title>When to Use Threading</title>
+
+ <p>
+ When writing projects using GLib, the default approach should be to
+ <em style="strong">never use threads</em>. Instead, make proper use of the
+ <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html">GLib
+ main context</link> which, through the use of asynchronous operations,
+ allows most blocking I/O operations to continue in the background while
+ the main context continues to process other events. Analysis, review and
+ debugging of threaded code becomes very hard, very quickly.
+ </p>
+
+ <p>
+ Threading should only be necessary when using an external library which
+ has blocking functions which need to be called from GLib code. If the
+ library provides a non-blocking alternative, or one which integrates with
+ a
+ <link
href="http://pubs.opengroup.org/onlinepubs/009695399/functions/poll.html"><code>poll()</code></link>
+ loop, that should be used in preference. If the blocking function really
+ must be used, a thin wrapper should be written for it to convert it to the
+ normal
+ <link href="https://developer.gnome.org/gio/stable/GAsyncResult.html"><code>GAsyncResult</code>
+ style</link> of GLib asynchronous function, running the blocking operation
+ in a worker thread.
+ </p>
+
+ <example>
+ <p>
+ For example:
+ </p>
+ <code mime="text/x-csrc">
+int
+some_blocking_function (void *param1,
+ void *param2);
+</code>
+ <p>
+ Should be wrapped as:
+ </p>
+ <code mime="text/x-csrc">
+void
+some_blocking_function_async (void *param1,
+ void *param2,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+int
+some_blocking_function_finish (GAsyncResult *result,
+ GError **error);
+</code>
+
+ <p>
+ With an implementation something like:
+ </p>
+ <code mime="text/x-csrc">/* Closure for the call’s parameters. */
+typedef struct {
+ void *param1;
+ void *param2;
+} SomeBlockingFunctionData;
+
+static void
+some_blocking_function_data_free (SomeBlockingFunctionData *data)
+{
+ free_param (data->param1);
+ free_param (data->param2);
+
+ g_free (data);
+}
+
+static void
+some_blocking_function_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ SomeBlockingFunctionData *data = task_data;
+ int retval;
+
+ /* Handle cancellation. */
+ if (g_task_return_error_if_cancelled (task))
+ {
+ return;
+ }
+
+ /* Run the blocking function. */
+ retval = some_blocking_function (data->param1, data->param2);
+ g_task_return_int (task, retval);
+}
+
+void
+some_blocking_function_async (void *param1,
+ void *param2,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task = NULL; /* owned */
+ SomeBlockingFunctionData *data = NULL; /* owned */
+
+ g_return_if_fail (validate_param (param1));
+ g_return_if_fail (validate_param (param2));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (NULL, cancellable, callback, user_data);
+ g_task_set_source_tag (task, some_blocking_function_async);
+
+ /* Cancellation should be handled manually using mechanisms specific to
+ * some_blocking_function(). */
+ g_task_set_return_on_cancel (task, FALSE);
+
+ /* Set up a closure containing the call’s parameters. Copy them to avoid
+ * locking issues between the calling thread and the worker thread. */
+ data = g_new0 (SomeBlockingFunctionData, 1);
+ data->param1 = copy_param (param1);
+ data->param2 = copy_param (param2);
+
+ g_task_set_task_data (task, data, some_blocking_function_data_free);
+
+ /* Run the task in a worker thread and return immediately while that continues
+ * in the background. When it’s done it will call @callback in the current
+ * thread default main context. */
+ g_task_run_in_thread (task, some_blocking_function_thread_cb);
+
+ g_object_unref (task);
+}
+
+int
+some_blocking_function_finish (GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result,
+ some_blocking_function_async), -1);
+ g_return_val_if_fail (error == NULL || *error == NULL, -1);
+
+ return g_task_propagate_int (G_TASK (result), error);
+}</code>
+ <p>
+ See the
+ <link href="https://developer.gnome.org/gio/stable/GAsyncResult.html"><code>GAsyncResult</code>
+ documentation</link> for more details. A simple way to implement the
+ worker thread is to use
+ <link href="https://developer.gnome.org/gio/stable/GTask.html"><code>GTask</code></link>
+ and <link
href="https://developer.gnome.org/gio/stable/GTask.html#g-task-run-in-thread"><code>g_task_run_in_thread()</code></link>.
+ </p>
+ </example>
+ </section>
+
+ <section id="using-threading">
+ <title>Using Threading</title>
+
+ <p>
+ If <code>GTask</code> is not suitable for writing the worker thread, a
+ more low-level approach must be used. This should be considered very
+ carefully, as it is very easy to get threading code wrong in ways which
+ will unpredictably cause bugs at runtime, cause deadlocks, or consume too
+ many resources and terminate the program.
+ </p>
+
+ <p>
+ A full manual on writing threaded code is beyond the scope of this
+ document, but here are a number of guidelines to follow which should
+ reduce the potential for bugs in threading code. The overriding principle
+ is to reduce the amount of code and data which can be affected by
+ threading — for example, reducing the number of threads, the complexity of
+ worker thread implementation, and the amount of data shared between
+ threads.
+ </p>
+
+ <list>
+ <item>
+ <p>
+ Use <link
href="https://developer.gnome.org/glib/stable/glib-Thread-Pools.html"><code>GThreadPool</code></link>
+ instead of manually creating
+ <link href="https://developer.gnome.org/glib/stable/glib-Threads.html"><code>GThread</code>s</link>
+ if possible. <code>GThreadPool</code> supports a work queue, limits on
+ the number of spawned threads, and automatically joins finished
+ threads so they are not leaked.
+ </p>
+ </item>
+ <item>
+ <p>
+ If it is not possible to use a <code>GThreadPool</code> (which is
+ rarely the case):
+ </p>
+
+ <list>
+ <item>
+ <p>
+ Use <link
href="https://developer.gnome.org/glib/stable/glib-Threads.html#g-thread-try-new"><code>g_thread_try_new()</code></link>
+ to spawn threads, instead of
+ <link
href="https://developer.gnome.org/glib/stable/glib-Threads.html#g-thread-new"><code>g_thread_new()</code></link>,
+ so errors due to the system running out of threads can be handled
+ gracefully rather than unconditionally aborting the program.
+ </p>
+ </item>
+ <item>
+ <p>
+ Explicitly join threads using
+ <link
href="https://developer.gnome.org/glib/stable/glib-Threads.html#g-thread-join"><code>g_thread_join()</code></link>
+ to avoid leaking the thread resources.
+ </p>
+ </item>
+ </list>
+ </item>
+ <item>
+ <p>
+ Use message passing to transfer data between threads, rather than
+ manual locking with mutexes. <code>GThreadPool</code> explicitly
+ supports this with
+ <link
href="https://developer.gnome.org/glib/stable/glib-Thread-Pools.html#g-thread-pool-push"><code>g_thread_pool_push()</code></link>.
+ </p>
+ </item>
+ <item>
+ <p>
+ If mutexes must be used:
+ </p>
+
+ <list>
+ <item>
+ <p>
+ Isolate threading code as much as possible, keeping mutexes
+ private within classes, and tightly bound to very specific class
+ members.
+ </p>
+ </item>
+ <item>
+ <p>
+ All mutexes should be clearly commented beside their declaration,
+ indicating which other structures or variables they protect access
+ to. Similarly, those variables should be commented saying that
+ they should <em>only</em> be accessed with that mutex held.
+ </p>
+ </item>
+ </list>
+ </item>
+ <item>
+ <p>
+ Be careful about interactions between main contexts and threads. For
+ example,
+ <link
href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-timeout-add-seconds"><code>g_timeout_add_seconds()</code></link>
+ adds a timeout <em>to be executed in the global default main
+ context</em>, which is being run in the main thread, <em>not
+ necessarily</em> the current thread. Getting this wrong can mean that
+ work intended for a worker thread accidentally ends up being executed
+ in the main thread anyway.
+ </p>
+ </item>
+ </list>
+ </section>
+
+ <section id="debugging">
+ <title>Debugging</title>
+
+ <p>
+ Debugging threading issues is tricky, both because they are hard to
+ reproduce, and because they are hard to reason about. This is one of the
+ big reasons for avoiding using threads in the first place.
+ </p>
+
+ <p>
+ However, if a threading issue does arise,
+ <link xref="tooling#helgrind-and-drd">Valgrind’s drd and helgrind tools
+ are useful</link>.
+ </p>
+ </section>
+</page>
diff --git a/programming-guidelines/Makefile.am b/programming-guidelines/Makefile.am
index d171609..bb5acbd 100644
--- a/programming-guidelines/Makefile.am
+++ b/programming-guidelines/Makefile.am
@@ -14,6 +14,7 @@ HELP_FILES = \
index.page \
memory-management.page \
parallel-installability.page \
+ threading.page \
tooling.page \
writing-good-code.page \
$(NULL)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]