glib中的陷阱

Table of Contents

GLib is a general-purpose, portable utility library, which provides many useful data types, macros, type conversions, string utilities, file utilities, a mainloop abstraction, and so on.

在 Linux 系统上做 C/C++ 语言的开发, glib 是常用的 library, 大量的的 library 也基于它, 例如 gstreamer, libsoup 等大名鼎鼎的工具和库.

What is GMainContext?

GMainContext is a generalized implementation of an event loop, useful for implementing polled file I/O or event-based widget systems (such as GTK). It is at the core of almost every GLib application. To understand GMainContext requires understanding poll() and polled I/O.

GMainContext 是事件循环的通用实现,可用于实现轮询文件 I/O 或基于事件的桌面小组件系统(例如 GTK)。它是几乎所有 GLib 应用程序的核心。要理解 GMainContext,需要理解 poll() 和轮询 I/O。

A GMainContext has a set of sources which are ‘attached’ to it, each of which can be thought of as an expected event with an associated callback function which will be invoked when that event is received; or equivalently as a set of file descriptors (FDs) to check. An event could be a timeout or data being received on a socket, for example.

GMainContext 具有一组“附加”到它的源,每个源都可以被视为一个预期事件,并带有一个关联的回调函数,该函数将在收到该事件时调用;或者等同于一组要检查的文件描述符 (FD)。例如,事件可能是超时或在套接字上接收数据。

One iteration of the event loop will:

  • Prepare sources, determining if any of them are ready to dispatch immediately.
  • Poll the sources, blocking the current thread until an event is received for one of the sources.
  • Check which of the sources received an event (several could have).
  • Dispatch callbacks from those sources.

事件循环的一次迭代将:

  • 准备源,确定其中是否有任何源已准备好立即分派。

  • 轮询源,阻止当前线程,直到收到其中一个源的事件。

  • 检查哪些源接收到了事件(可能有多个)。

  • 从这些源分派回调。

At its core, GMainContext is just a poll() loop, with the preparation, check and dispatch stages of the loop corresponding to the normal preamble and postamble in a typical poll() loop implementation. Typically, some complexity is needed in non-trivial poll()-using applications to track the lists of FDs which are being polled. Additionally, GMainContext adds a lot of useful functionality which vanilla poll() doesn’t support. Most importantly, it adds thread safety.

GMainContext is completely thread safe, meaning that a GSource can be created in one thread and attached to a GMainContext running in another thread. A typical use for this might be to allow worker threads to control which sockets are being listened to by a GMainContext in a central I/O thread. Each GMainContext is ‘acquired’ by a thread for each iteration it’s put through. Other threads cannot iterate a GMainContext without acquiring it, which guarantees that a GSource and its FDs will only be polled by one thread at once (since each GSource is attached to at most one GMainContext). A GMainContext can be swapped between threads across iterations, but this is expensive.

GMainContext is used instead of poll() mostly for convenience, as it transparently handles dynamically managing the array of FDs to pass to poll(), especially when operating over multiple threads. This is done by encapsulating file descriptors inside a GSource, which decide whether those FDs should be passed to the poll() call on each ‘prepare’ stage of the main context iteration.

从本质上讲,GMainContext 只是一个 poll() 循环,循环的准备、检查和调度阶段对应于典型 poll() 循环实现中的正常前导和后导。通常,在使用 poll() 的复杂应用程序中,需要一些复杂性来跟踪正在轮询的 FD 列表。此外,GMainContext 还添加了许多有用的功能,而普通的 poll() 不支持这些功能。最重要的是,它增加了线程安全性。

GMainContext 是完全线程安全的,这意味着可以在一个线程中创建 GSource 并将其附加到在另一个线程中运行的 GMainContext。这种做法的典型用途可能是允许工作线程控制中央 I/O 线程中的 GMainContext 正在监听哪些套接字。每个 GMainContext 都会被线程“获取”,用于每次迭代。其他线程无法在未获取 GMainContext 的情况下对其进行迭代,这保证了 GSource 及其 FD 一次只能被一个线程轮询(因为每个 GSource 最多附加到一个 GMainContext)。GMainContext 可以在迭代之间在线程之间交换,但这样做成本很高。

使用 GMainContext 代替 poll() 主要是为了方便,因为它可以透明地动态管理要传递给 poll() 的 FD 数组,尤其是在多线程操作时。这是通过将文件描述符封装在 GSource 中来实现的,GSource 决定是否应在主上下文迭代的每个“准备”阶段将这些 FD 传递给 poll() 调用。

What is GMainLoop?

GMainLoop is essentially the following few lines of code, once reference counting and locking have been removed:

loop->is_running = TRUE;
while (loop->is_running)
  {
    if (quit_condition)
      loop->is_running = FALSE;

    g_main_context_iteration (context, TRUE);
  }

Setting quit_condition to TRUE will cause the loop to terminate once the current main context iteration ends.

Hence, GMainLoop is a convenient, thread-safe way of running a GMainContext to process events until a desired exit condition is met, at which point g_main_loop_quit() should be called. Typically, in a UI program, this will be the user clicking ‘exit’. In a socket handling program, this might be the final socket closing.

It is important not to confuse main contexts with main loops. Main contexts do the bulk of the work: preparing source lists, waiting for events, and dispatching callbacks. A main loop simply iterates a context.

将 quit_condition 设置为 TRUE 将导致循环在当前 main contxt 迭代结束后终止。

因此,GMainLoop 是一种方便、线程安全的方式,用于运行 GMainContext 来处理事件,直到满足所需的退出条件,此时应调用 g_main_loop_quit()。通常,在 UI 程序中,这将是用户单击“退出”。在套接字处理程序中,这可能是最后的套接字关闭。

重要的是不要将 main context 与 main loop 混淆。main context 完成大部分工作:准备源列表、等待事件和调度回调。main loop 只是迭代 main context。

Default Contexts 默认上下文

One of the important features of GMainContext is its support for ‘default’ contexts. There are two levels of default context: the thread-default, and the global-default. The global-default (accessed using g_main_context_default()) is run by GTK when g_application_run() is called. It’s also used for timeouts (g_timeout_add()) and idle callbacks (g_idle_add()) — these won’t be dispatched unless the default context is running!

Thread-default contexts are generally used for I/O operations which need to run and dispatch callbacks in a thread. By calling g_main_context_push_thread_default() before starting an I/O operation, the thread-default context is set and the I/O operation can add its sources to that context. The context can then be run in a new main loop in an I/O thread, causing the callbacks to be dispatched on that thread’s stack rather than on the stack of the thread running the global-default main context. This allows I/O operations to be run entirely in a separate thread without explicitly passing a specific GMainContext pointer around everywhere.

Conversely, by starting a long-running operation with a specific thread-default context set, the calling code can guarantee that the operation’s callbacks will be emitted in that context, even if the operation itself runs in a worker thread. This is the principle behind GTask: when a new GTask is created, it stores a reference to the current thread-default context, and dispatches its completion callback in that context, even if the task itself is run using g_task_run_in_thread().

GMainContext 的一个重要特性是它支持“默认”上下文。默认上下文有两个级别:线程默认和全局默认。全局默认(使用 g_main_context_default() 访问)在调用 g_application_run() 时由 GTK 运行。它还用于超时(g_timeout_add())和空闲回调(g_idle_add())——除非默认上下文正在运行,否则不会分派这些回调!

线程默认上下文通常用于需要在线程中运行和分派回调的 I/O 操作。通过在启动 I/O 操作之前调用 g_main_context_push_thread_default(),将设置线程默认上下文,并且 I/O 操作可以将其源添加到该上下文。然后可以在 I/O 线程中的新主循环中运行上下文,从而导致回调在该线程的堆栈上分派,而不是在运行全局默认主上下文的线程的堆栈上分派。这样就可以在单独的线程中完全运行 I/O 操作,而无需到处明确传递特定的 GMainContext 指针。

相反,通过启动具有特定线程默认上下文集的长时间运行的操作,调用代码可以保证操作的回调将在该上下文中发出,即使操作本身在工作线程中运行。这是 GTask 背后的原理:创建新的 GTask 时,它会存储对当前线程默认上下文的引用,并在该上下文中调度其完成回调,即使任务本身是使用 g_task_run_in_thread() 运行的。

For example, the code below will run a GTask which performs two writes in parallel from a thread. The callbacks for the writes will be dispatched in the worker thread, whereas the callback from the task as a whole will be dispatched in the interesting_context.

例如,下面的代码将运行一个 GTask,该 GTask 从一个线程并行执行两个写入操作。写入的回调将在工作线程中调度,而整个任务的回调将在 interesting_context 中调度。

typedef struct {
  GMainLoop *main_loop;
  guint n_remaining;
} WriteData;

/* This is always called in the same thread as thread_cb() because
 * it’s always dispatched in the @worker_context. */
static void
write_cb (GObject      *source_object,
          GAsyncResult *result,
          gpointer      user_data)
{
  WriteData *data = user_data;
  GOutputStream *stream = G_OUTPUT_STREAM (source_object);
  GError *error = NULL;
  gssize len;

  /* Finish the write. */
  len = g_output_stream_write_finish (stream, result, &error);
  if (error != NULL)
    {
      g_error ("Error: %s", error->message);
      g_error_free (error);
    }

  /* Check whether all parallel operations have finished. */
  write_data->n_remaining--;

  if (write_data->n_remaining == 0)
    {
      g_main_loop_quit (write_data->main_loop);
    }
}

/* This is called in a new thread. */
static void
thread_cb (GTask        *task,
           gpointer      source_object,
           gpointer      task_data,
           GCancellable *cancellable)
{
  /* These streams come from somewhere else in the program: */
  GOutputStream *output_stream1, *output_stream;
  GMainContext *worker_context;
  GBytes *data;
  const guint8 *buf;
  gsize len;

  /* Set up a worker context for the writes’ callbacks. */
  worker_context = g_main_context_new ();
  g_main_context_push_thread_default (worker_context);

  /* Set up the writes. */
  write_data.n_remaining = 2;
  write_data.main_loop = g_main_loop_new (worker_context, FALSE);

  data = g_task_get_task_data (task);
  buf = g_bytes_get_data (data, &len);

  g_output_stream_write_async (output_stream1, buf, len,
                               G_PRIORITY_DEFAULT, NULL, write_cb,
                               &write_data);
  g_output_stream_write_async (output_stream2, buf, len,
                               G_PRIORITY_DEFAULT, NULL, write_cb,
                               &write_data);

  /* Run the main loop until both writes have finished. */
  g_main_loop_run (write_data.main_loop);
  g_task_return_boolean (task, TRUE);  /* ignore errors */

  g_main_loop_unref (write_data.main_loop);

  g_main_context_pop_thread_default (worker_context);
  g_main_context_unref (worker_context);
}

/* This can be called from any thread. Its @callback will always be
 * dispatched in the thread which currently owns
 * @interesting_context. */
void
parallel_writes_async (GBytes              *data,
                       GMainContext        *interesting_context,
                       GCancellable        *cancellable,
                       GAsyncReadyCallback  callback,
                       gpointer             user_data)
{
  GTask *task;

  g_main_context_push_thread_default (interesting_context);

  task = g_task_new (NULL, cancellable, callback, user_data);
  g_task_set_task_data (task, data,
                        (GDestroyNotify) g_bytes_unref);
  g_task_run_in_thread (task, thread_cb);
  g_object_unref (task);

  g_main_context_pop_thread_default (interesting_context);
}

Implicit Use of the Global-Default Main Context

Several functions implicitly add sources to the global-default main context. They should not be used in threaded code. Instead, use g_source_attach() with the GSource created by the replacement function from the table below.

Implicit use of the global-default main context means the callback functions are invoked in the main thread, typically resulting in work being brought back from a worker thread into the main thread.

多个函数会隐式将源添加到全局默认主上下文中。它们不应在线程代码中使用。相反,应将 g_source_attach() 与下表中替换函数创建的 GSource 结合使用。

隐式使用全局默认主上下文意味着回调函数在主线程中调用,通常会导致工作从工作线程带回到主线程。

Do not use Use instead
g_timeout_add() g_timeout_source_new()
g_idle_add() g_idle_source_new()
g_child_watch_add() g_child_watch_source_new()

So to delay some computation in a worker thread, use the following code:

static guint
schedule_computation (guint delay_seconds)
{
  /* Get the calling context. */
  GMainContext *context = g_main_context_get_thread_default ();

  GSource *source = g_timeout_source_new_seconds (delay_seconds);
  g_source_set_callback (source, do_computation, NULL, NULL);

  guint id = g_source_attach (source, context);

  g_source_unref (source);

  /* The ID can be used with the same @context to
   * cancel the scheduled computation if needed. */
  return id;
}

static void
do_computation (gpointer user_data)
{
  // ...
}

Using GMainContext in a Library

At a high level, library code must not make changes to main contexts which could affect the execution of an application using the library, for example by changing when the application’s sources are dispatched. There are various best practices which can be followed to aid this.

Never iterate a context created outside the library, including the global-default or thread-default contexts. Otherwise, sources created in the application may be dispatched when the application is not expecting it, causing re-entrancy problems for the application code.

从高层次上讲,库代码不得对主上下文进行更改,因为这可能会影响使用该库的应用程序的执行,例如更改应用程序源的调度时间。有各种最佳实践可以遵循以协助实现这一点。

切勿迭代使用在库以外创建的上下文(context),包括全局默认(global-default)或线程默认(thread-default)上下文。否则,在应用程序中创建的源可能会在应用程序不期望时被调度,从而导致应用程序代码的重入问题。

Always remove sources from a main context before dropping the library’s last reference to the context, especially if it may have been exposed to the application (for example, as a thread-default). Otherwise the application may keep a reference to the main context and continue iterating it after the library has returned, potentially causing unexpected source dispatches in the library. This is equivalent to not assuming that dropping the library’s last reference to a main context will finalize that context.

在删除软件库对上下文的最后一个引用之前,始终要先从主上下文中删除源(source),特别是如果它可能已暴露给应用程序(例如,作为线程默认)。否则,应用程序可能会保留对主上下文的引用并在库返回后继续迭代它,这可能会导致库中出现意外的源调度。这相当于不假设删除库对主上下文的最后一个引用将完成该上下文。

If the library is designed to be used from multiple threads, or in a context-aware fashion, always document which context each callback will be dispatched in. For example, “callbacks will always be dispatched in the context which is the thread-default at the time of the object’s construction”. Developers using the library’s API need to know this information.

如果库设计为从多个线程使用,或以上下文感知的方式使用,则始终记录每个回调将在哪个上下文中分派。例如,“回调将始终在对象构造时的线程默认上下文中分派”。使用库的 API 的开发人员需要知道此信息。

Use g_main_context_invoke() to ensure callbacks are dispatched in the right context. It’s much easier than manually using g_idle_source_new() to transfer work between contexts.

Libraries should never use g_main_context_default() (or, equivalently, pass NULL to a GMainContext-typed parameter). Always store and explicitly use a specific GMainContext, even if it often points to some default context. This makes the code easier to split out into threads in future, if needed, without causing hard-to-debug problems caused by callbacks being invoked in the wrong context.

使用 g_main_context_invoke() 确保回调在正确的上下文中分派。这比手动使用 g_idle_source_new() 在上下文之间传输工作要容易得多。

库永远不应使用 g_main_context_default()(或等效地将 NULL 传递给 GMainContext 类型的参数)。始终存储并明确使用特定的 GMainContext,即使它经常指向某个默认上下文。这使得代码在将来更容易拆分成线程(如果需要),而不会导致由于在错误的上下文中调用回调而导致难以调试的问题。

Write things asynchronously internally (using GTask where appropriate), and keep synchronous wrappers at the very top level of an API, where they can be implemented by calling g_main_context_iteration() on a specific GMainContext. Again, this makes future refactoring easier. This is demonstrated in the previous example: the thread uses g_output_stream_write_async() rather than g_output_stream_write(). A worker thread may be used instead, and this can simplify the callback chain for long series of asynchronous calls; but at the cost of increased complexity in verifying the code is race-free.

Always match pushes and pops of the thread-default main context: g_main_context_push_thread_default() and g_main_context_pop_thread_default().

在内部异步写入内容(在适当的情况下使用 GTask),并将同步包装器保持在 API 的最顶层,在那里可以通过在特定 GMainContext 上调用 g_main_context_iteration() 来实现它们。同样,这使得未来的重构更容易。这在前面的示例中得到了演示:线程使用 g_output_stream_write_async() 而不是 g_output_stream_write()。可以使用工作线程来代替,这可以简化长系列异步调用的回调链;但代价是增加了验证代码是否无竞争的复杂性。

始终匹配线程默认主上下文的推送和弹出:g_main_context_push_thread_default() 和 g_main_context_pop_thread_default()。

Ensuring Functions are Called in the Right Context

确保在正确的上下文中调用函数

The ‘right context’ is the thread-default main context of the thread the function should be executing in. This assumes the typical case that every thread has a single main context running in a main loop. A main context effectively provides a work or message queue for the thread — something which the thread can periodically check to determine if there is work pending from another thread. Putting a message on this queue – invoking a function in another main context – will result in it eventually being dispatched in that thread.

“正确的上下文”是函数应在其中执行的线程的线程默认主上下文。这假设典型情况是每个线程都有一个在主循环中运行的主上下文。主上下文有效地为线程提供了工作或消息队列——线程可以定期检查以确定是否有来自另一个线程的待处理工作。将消息放在这个队列上——在另一个主上下文中调用一个函数——最终将导致它在该线程中被分派。

For example, if an application does a long and CPU-intensive computation it should schedule this in a background thread so that UI updates in the main thread are not blocked. The results of the computation, however, might need to be displayed in the UI, so some UI update function must be called in the main thread once the computation’s complete.

例如,如果应用程序执行长时间且 CPU 密集型的计算,则应将其安排在后台线程中,以便主线程中的 UI 更新不会被阻止。但是,计算结果可能需要显示在 UI 中,因此一旦计算完成,必须在主线程中调用一些 UI 更新函数。

Furthermore, if the computation function can be limited to a single thread, it becomes easy to eliminate the need for locking a lot of the data it accesses. This assumes that other threads are implemented similarly and hence most data is only accessed by a single thread, with threads communicating by message passing. This allows each thread to update its data at its leisure, which significantly simplifies locking.

此外,如果计算函数可以限制在单个线程中,那么就很容易消除锁定它访问的大量数据的需要。这假设其他线程的实现方式类似,因此大多数数据仅由单个线程访问,线程通过消息传递进行通信。这允许每个线程在空闲时更新其数据,从而大大简化锁定。

For some functions, there might be no reason to care which context they’re executed in, perhaps because they’re asynchronous and hence do not block the context. However, it is still advisable to be explicit about which context is used, since those functions may emit signals or invoke callbacks, and for reasons of thread safety it’s necessary to know which threads those signal handlers or callbacks are going to be invoked in.

For example, the progress callback in g_file_copy_async() is documented as being called in the thread-default main context at the time of the initial call.

对于某些函数,可能没有理由关心它们在哪个上下文中执行,可能是因为它们是异步的,因此不会阻止上下文。但是,仍然建议明确使用哪个上下文,因为这些函数可能会发出信号或调用回调,并且出于线程安全的原因,有必要知道将在哪些线程中调用这些信号处理程序或回调。

例如,g_file_copy_async() 中的进度回调被记录为在初始调用时在线程默认主上下文中调用。

Principles of Invocation

调用原则

The core principle of invoking a function in a specific context is simple, and is walked through below to explain the concepts. In practice the convenience method, g_main_context_invoke_full() should be used instead.

在特定上下文中调用函数的核心原则很简单,下面将逐步介绍这些概念。在实践中,应改用便捷方法 g_main_context_invoke_full()。

A GSource has to be added to the target GMainContext, which will invoke the function when it’s dispatched. This GSource should almost always be an idle source created with g_idle_source_new(), but this doesn’t have to be the case. It could be a timeout source so that the function is executed after a delay, for example.

必须将 GSource 添加到目标 GMainContext,它将在分派时调用该函数。此 GSource 几乎始终是使用 g_idle_source_new() 创建的空闲源,但不一定如此。例如,它可以是超时源,以便函数在延迟后执行。

The GSource will be dispatched as soon as it’s ready, calling the function on the thread’s stack. In the case of an idle source, this will be as soon as all sources at a higher priority have been dispatched — this can be tweaked using the idle source’s priority parameter with g_source_set_priority(). The source will typically then be destroyed so the function is only executed once (though again, this doesn’t have to be the case).

GSource 将在准备就绪后立即分派,并在线程堆栈上调用该函数。对于空闲源,这将在更高优先级的所有源都已分派后立即进行 — 可以使用空闲源的优先级参数和 g_source_set_priority() 进行调整。然后通常会销毁源,以便函数仅执行一次(但同样,不一定如此)。

Data can be passed between threads as the user_data passed to the GSource’s callback. This is set on the source using g_source_set_callback(), along with the callback function to invoke. Only a single pointer is provided, so if multiple data fields need passing, they must be wrapped in an allocated structure.

数据可以在线程之间传递,因为 user_data 会传递给 GSource 的回调。这是使用 g_source_set_callback() 在源上设置的,以及要调用的回调函数。只提供了一个指针,因此如果需要传递多个数据字段,则必须将它们包装在分配的结构中。

下面的示例演示了底层原理,但下面解释了一些简便的方法,可以简化事情。

/* Main function for the background thread, thread1. */
static gpointer
thread1_main (gpointer user_data)
{
  GMainContext *thread1_main_context = user_data;
  GMainLoop *main_loop;

  /* Set up the thread’s context and run it forever. */
  g_main_context_push_thread_default (thread1_main_context);

  main_loop = g_main_loop_new (thread1_main_context, FALSE);
  g_main_loop_run (main_loop);
  g_main_loop_unref (main_loop);

  g_main_context_pop_thread_default (thread1_main_context);
  g_main_context_unref (thread1_main_context);

  return NULL;
}

/* A data closure structure to carry multiple variables between
 * threads. */
typedef struct {
  gchar   *some_string;  /* owned */
  guint    some_int;
  GObject *some_object;  /* owned */
} MyFuncData;

static void
my_func_data_free (MyFuncData *data)
{
  g_free (data->some_string);
  g_clear_object (&data->some_object);
  g_free (data);
}

static void
my_func (const gchar *some_string,
         guint        some_int,
         GObject     *some_object)
{
  /* Do something long and CPU intensive! */
}

/* Convert an idle callback into a call to my_func(). */
static gboolean
my_func_idle (gpointer user_data)
{
  MyFuncData *data = user_data;

  my_func (data->some_string, data->some_int, data->some_object);

  return G_SOURCE_REMOVE;
}

/* Function to be called in the main thread to schedule a call to
 * my_func() in thread1, passing the given parameters along. */
static void
invoke_my_func (GMainContext *thread1_main_context,
                const gchar  *some_string,
                guint         some_int,
                GObject      *some_object)
{
  GSource *idle_source;
  MyFuncData *data;

  /* Create a data closure to pass all the desired variables
   * between threads. */
  data = g_new0 (MyFuncData, 1);
  data->some_string = g_strdup (some_string);
  data->some_int = some_int;
  data->some_object = g_object_ref (some_object);

  /* Create a new idle source, set my_func() as the callback with
   * some data to be passed between threads, bump up the priority
   * and schedule it by attaching it to thread1’s context. */
  idle_source = g_idle_source_new ();
  g_source_set_callback (idle_source, my_func_idle, data,
                         (GDestroyNotify) my_func_data_free);
  g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
  g_source_attach (idle_source, thread1_main_context);
  g_source_unref (idle_source);
}

/* Main function for the main thread. */
static void
main (void)
{
  GThread *thread1;
  GMainContext *thread1_main_context;

  /* Spawn a background thread and pass it a reference to its
   * GMainContext. Retain a reference for use in this thread
   * too. */
  thread1_main_context = g_main_context_new ();
  g_thread_new ("thread1", thread1_main,
                g_main_context_ref (thread1_main_context));

  /* Maybe set up your UI here, for example. */

  /* Invoke my_func() in the other thread. */
  invoke_my_func (thread1_main_context,
                  "some data which needs passing between threads",
                  123456, some_object);

  /* Continue doing other work. */
}

此调用是单向的:它在线程 1 中调用 my_func(),但无法将值返回给主线程。为此,需要再次使用相同的原理,在主线程中调用回调函数。这是一个简单的扩展,本文不作介绍。

为了保持线程安全,可能被多个线程访问的数据必须使用互斥锁使这些访问互斥。可能被多个线程访问的数据:thread1_main_context,在对 thread1_main 的 fork 调用中传递;some_object,在数据闭包中传递对其的引用。至关重要的是,GLib 保证 GMainContext 是线程安全的,因此在线程之间共享 thread1_main_context 是安全的。该示例假设访问 some_object 的其他代码是线程安全的。

请注意,some_string 和 some_int 不能从两个线程访问,因为它们的副本而不是原始副本被传递给线程 1。这是一种使跨线程调用线程安全而无需锁定的标准技术。它还避免了同步释放 some_string 的问题。

类似地,对 some_object 的引用被转移到线程 1,这解决了同步销毁对象的问题。

使用 g_idle_source_new() 而不是更简单的 g_idle_add(),因此可以指定要附加到的 GMainContext。

Convenience Method: g_main_context_invoke_full()

This is simplified greatly by the convenience method, g_main_context_invoke_full(). It invokes a callback so that the specified GMainContext is owned during the invocation. Owning a main context is almost always equivalent to running it, and hence the function is invoked in the thread for which the specified context is the thread-default.

g_main_context_invoke() can be used instead if the user data does not need to be freed by a GDestroyNotify callback after the invocation returns.

Modifying the earlier example, the invoke_my_func() function can be replaced by the following:

Summary

  • Use g_main_context_invoke_full() to invoke functions in other threads, assuming every thread has a thread default main context which runs throughout the lifetime of that thread

  • Use GTask to run a function in the background without caring about the specific thread used

  • Liberally use assertions to check which context executes each function, and add these assertions when first writing the code

  • Explicitly document contexts a function is expected to be called in, a callback will be invoked in, or a signal will be emitted in

  • Beware of g_idle_add() and similar functions which implicitly use the global-default main context

Reference

Comments |0|

Legend *) Required fields are marked
**) You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
Category: 似水流年