Glib之主事件循環

介紹

GLib和GTK+應用的主事件循環管理着全部事件源。這些事件的來源有不少種好比文件描述符(文件、管道或套接字)或超時。新類型的事件源能夠經過g_source_attach()函數添加。
爲了讓多組獨立事件源可以在不一樣的線程中被處理,每一個事件源都會關聯一個GMainContext。一個線程只能運行一個GMainContext,可是在其餘線程中可以對事件源進行添加和刪除操做。
每一個事件源都被賦予了優先級。默認的優先級是G_PRIORITY_DEFAULT(0)。值越小優先級越高,優先級高的事件源優先處理。
Idle函數在沒有更高優先級的事件被處理的時候纔會執行。
GMainLoop數據類型表明了一個主事件循環。經過g_main_loop_new()來建立GMainLoop對象。在添加完初始事件源後執行g_main_loop_run(),主循環將持續不斷的檢查每一個事件源產生的新事件,而後分發它們,直處處理來自某個事件源的事件的時候觸發了g_main_loop_quit()調用退出主循環爲止。
GMainLoop實例可以被遞歸建立。在GTK+應用中常常使用這種方式來顯示模態對話框。注意若是一個事件源被添加到一個GMainContext,那麼它將被全部關聯這個GMainContext的主線程檢查和分發。
GTK+對這些函數作了些封裝,例如gtk_main、gtk_mian_quit和gtk_events_pending。數據結構

自定義事件類型

GMainLoop一個不經常使用的特性就是可以建立一個新的事件源類型,而後當作內置事件源的擴展來使用。一個新的事件源類型一般用來處理GDK事件。經過繼承GSource結構來建立一個新的事件源類型。繼承產生的新事件源類型表示GSource結構做爲新事件源類型的第一個元素而後其餘元素緊跟其後。使用g_source_new函數來建立新的事件源類型實例,函數的參數就是新的事件源類型大小。GSourceFuncs決定新的事件源類型的行爲。
新的事件源有兩種基本方式與GMainContext交互。它們GSourceFuncs中的準備函數可以設置睡眠事件,用來決定主循環下次檢測它們的時間。此外事件源也可使用g_source_add_poll()函數添加文件描述符到GMainContext進行檢測。函數

自定義主循環迭代

執行g_main_context_iteration()函數能夠完成GMainContext的單次迭代。在一些狀況下,咱們可能想獲取主循環更多的底層控制細節,咱們能夠調用g_main_context_iteration()裏的組件函數:g_main_context_prepare()g_main_context_prepare ()g_main_context_query()g_main_context_check()g_main_context_dispatch()oop

Main Context狀態

在UNIX系統上,GLib的主循環和fork()是不兼容的。ui

事件源內存管理

有兩種可選的方式來管理傳遞給GSource回調函數用戶數據的內存。用戶數據就是在調用g_timeout_add()g_timeout_add_full()g_idle_add()傳入的參數。這些數據一般被timeout或idle回調函數所擁有,好比一個構件或一個網路協議的實現。有些時候這些回調函數會在數據被銷燬的後背調用,由於使用了已經被釋放的內存,因此這會致使一個錯誤。spa

  • 第一種推薦的方法就是保存g_timeout_add()g_source_attach()返回的事件源ID,而後在其維持的用戶數據被釋放後顯示的將其從GMainContext移除。這樣就能保證調用這些回調函數的時候,這些用戶數據依然有效。
  • 第二種就是保存這些回調函數中的用戶數據對象的引用,而後在GDestroyNotify回調函數中釋放它。這樣就能確保數據對象在事件源最後一次調用而後被釋放前一直有效。GDestroyNotify回調函數是GSource函數的一個變體的入參,它在事件源被釋放時調用。
    第二條途徑有必要提醒下,若是在事件源還沒被調用前主循環就結束的狀況下,用戶數據對象被維持狀態是不肯定的。

代碼

數據結構

struct _GSourcePrivate
{
  GSList *child_sources;
  GSource *parent_source;

  gint64 ready_time;

  /* This is currently only used on UNIX, but we always declare it (and
   * let it remain empty on Windows) to avoid #ifdef all over the place.
   */
  GSList *fds;
};

struct _GSource
{
  /*< private >*/
  gpointer callback_data;
  GSourceCallbackFuncs *callback_funcs;

  const GSourceFuncs *source_funcs;
  guint ref_count;

  GMainContext *context;

  gint priority;
  guint flags;
  guint source_id;

  GSList *poll_fds;

  GSource *prev;
  GSource *next;

  char    *name;

  GSourcePrivate *priv;
};

struct _GPollRec
{
  GPollFD *fd;
  GPollRec *prev;
  GPollRec *next;
  gint priority;
};

struct _GMainContext
{
  /* The following lock is used for both the list of sources
   * and the list of poll records
   */
  GMutex mutex;
  GCond cond;
  GThread *owner;
  guint owner_count;
  GSList *waiters;

  gint ref_count;

  GHashTable *sources;              /* guint -> GSource */

  GPtrArray *pending_dispatches;
  gint timeout;            /* Timeout for current iteration */

  guint next_id;
  GList *source_lists;
  gint in_check_or_prepare;

  GPollRec *poll_records;
  guint n_poll_records;
  GPollFD *cached_poll_array;
  guint cached_poll_array_size;

  GWakeup *wakeup;

  GPollFD wake_up_rec;

/* Flag indicating whether the set of fd's changed during a poll */
  gboolean poll_changed;

  GPollFunc poll_func;

  gint64   time;
  gboolean time_is_fresh;
};

struct _GMainLoop
{
  GMainContext *context;
  gboolean is_running;
  gint ref_count;
};
函數 說明
g_main_context_add_poll_unlocked 在g_source_add_poll和g_source_add_unix_fd中被調用,首先將須要監聽的文件描述符保存到_GSource->poll_fds或_GSource->priv->fds中,而後再建立個_GPollRec對象,接着將其保存到_GMainContext->poll_records中,最後設置context->poll_changed爲True,這個值會影響當前主循環的g_main_context_check
g_source_attach_unlocked 在g_source_attach和g_source_add_child_source中被調用,將_GSource保存到_GMainContext->sources,並將_GSource保存的文件描述符經過g_main_context_add_poll_unlocked函數保存到_GMainContext,最後返回_GSource->source_id
g_main_context_prepare 遍歷_GMainContext擁有的事件源,調用事件源的_GSourceFuncs->prepare函數計算下次輪訓間隔,並檢測事件源是否已經就緒
g_main_context_query 從_GMainContext擁有的事件源中篩選出須要進行poll的事件源,並設置context->poll_changed爲False(只要不是在poll期間對事件源進行添加或刪除操做便可)
g_main_context_poll 使用poll函數監聽g_main_context_query篩選出的事件源
g_main_context_check 遍歷g_main_context_query篩選出的事件源,調用事件源的_GSourceFuncs->check函數檢測事件源是否已經就緒,若是就緒則將事件源添加到_GMainContext->pending_dispatches中。若是context->poll_changed爲True(說明在poll的時候有新的事件源加入或移除),則跳事後面的分發(pending_dispatches爲空),從新執行g_main_context_iterate
g_main_context_dispatch 遍歷_GMainContext->pending_dispatches中的事件源,並調用事件源的_GSourceFuncs->dispatch函數進行分發

idle事件源

#define G_PRIORITY_DEFAULT_IDLE     200

GSourceFuncs g_idle_funcs =
{
  g_idle_prepare,
  g_idle_check,
  g_idle_dispatch,
  NULL
};

/* Idle functions */

static gboolean g_idle_prepare(GSource *source, gint *timeout)
{
  *timeout = 0;

  return TRUE;
}

static gboolean g_idle_check(GSource *source)
{
  return TRUE;
}

static gboolean g_idle_dispatch (GSource *source, GSourceFunc callback, gpointer user_data)
{
  gboolean again;

  if (!callback)
    {
      g_warning ("Idle source dispatched without callback\n"
         "You must call g_source_set_callback().");
      return FALSE;
    }

  again = callback (user_data);

  TRACE (GLIB_IDLE_DISPATCH (source, source->context, callback, user_data, again));

  return again;
}
函數 說明
g_idle_add 建立一個idle事件源,而後添加到GMainContext

timeout事件源

#define G_PRIORITY_DEFAULT          0

GSourceFuncs g_timeout_funcs =
{
  NULL, /* prepare */
  NULL, /* check */
  g_timeout_dispatch,
  NULL
};

static gboolean g_timeout_dispatch (GSource *source, GSourceFunc callback, gpointer user_data)
{
  GTimeoutSource *timeout_source = (GTimeoutSource *)source;
  gboolean again;

  if (!callback)
    {
      g_warning ("Timeout source dispatched without callback\n"
                 "You must call g_source_set_callback().");
      return FALSE;
    }

  again = callback (user_data);

  TRACE (GLIB_TIMEOUT_DISPATCH (source, source->context, callback, user_data, again));

  if (again)
    g_timeout_set_expiration (timeout_source, g_source_get_time (source));

  return again;
}
函數 說明
g_timeout_add 建立一個timeout事件源,而後添加到GMainContext。timeout事件源沒有本身的prepare和check函數,是由於在g_main_context_prepare和g_main_context_check 中都會獲取下當前系統時間,而後和timeout事件源的對比,若是時間已通過了,則將事件源的狀態修改成就緒狀態,因此就不須要本身來寫了

GIOChannel事件源

struct _GIOChannel
{
  /*< private >*/
  gint ref_count;
  GIOFuncs *funcs;

  gchar *encoding;
  GIConv read_cd;
  GIConv write_cd;
  gchar *line_term; /* String which indicates the end of a line of text */
  guint line_term_len; /* So we can have null in the line term */

  gsize buf_size;
  GString *read_buf; /* Raw data from the channel */
  GString *encoded_read_buf;    /* Channel data converted to UTF-8 */
  GString *write_buf; /* Data ready to be written to the file */
  gchar partial_write_buf[6];    /* UTF-8 partial characters, null terminated */

  /* Group the flags together, immediately after partial_write_buf, to save memory */

  guint use_buffer     : 1;    /* The encoding uses the buffers */
  guint do_encode      : 1;    /* The encoding uses the GIConv coverters */
  guint close_on_unref : 1;    /* Close the channel on final unref */
  guint is_readable    : 1;    /* Cached GIOFlag */
  guint is_writeable   : 1;    /* ditto */
  guint is_seekable    : 1;    /* ditto */

  gpointer reserved1;
  gpointer reserved2;
};

struct _GIOFuncs
{
  GIOStatus (*io_read)(GIOChannel *channel, gchar *buf, gsize count, gsize *bytes_read, GError **err);
  GIOStatus (*io_write)(GIOChannel *channel, const gchar *buf, gsize count, gsize *bytes_written, GError **err);
  GIOStatus (*io_seek)(GIOChannel *channel, gint64 offset, GSeekType type, GError **err);
  GIOStatus (*io_close)(GIOChannel *channel, GError **err);
  GSource* (*io_create_watch)(GIOChannel *channel, GIOCondition  condition);
  void (*io_free)(GIOChannel *channel);
  GIOStatus (*io_set_flags)(GIOChannel *channel, GIOFlags flags, GError **err);
  GIOFlags (*io_get_flags)(GIOChannel *channel);
};

GSourceFuncs g_io_watch_funcs = {
  g_io_unix_prepare,
  g_io_unix_check,
  g_io_unix_dispatch,
  g_io_unix_finalize
};

static GIOFuncs unix_channel_funcs = {
  g_io_unix_read,
  g_io_unix_write,
  g_io_unix_seek,
  g_io_unix_close,
  g_io_unix_create_watch,
  g_io_unix_free,
  g_io_unix_set_flags,
  g_io_unix_get_flags,
};
函數 說明
g_io_channel_new_file 獲取並保存文件句柄,而後使用unix_channel_funcs初始化_GIOChannel->funcs
g_io_create_watch 使用g_io_unix_create_watch函數將channel保存的文件句柄轉換爲事件源,事件源函數爲g_io_watch_funcs
g_io_add_watch 使用g_io_create_watch建立事件源,而後附加到當前主循環的GMainContext
g_io_unix_prepare 獲取channel的輸入輸出buffer的數據狀態,並與須要監聽的channel事件進行對比
g_io_unix_check 獲取channel的輸入輸出buffer的數據狀態,並與channel實際觸發的事件進行對比
相關文章
相關標籤/搜索