Gobject tutorial 十

参考:GLib – 2.0: The Main Event Loop

The Main Event Loop

主事件循环管理所有可用的事件源,事件可以是各种类型、各种数量的。比如说文件描述符(普通文件、管道以及套接字)和超时。

新类型的事件源可以通过函数g_source_attach来添加。为了使多个相互独立的事件源集能在不同的线程中进行处理,每个事件源都会关联一个GMainContext。一个GMainContext只能在一个线程中运行,但, 事件源可以添加到一个线程中的GmainContext,而从另外一个线程中移除。

我们来先来看看GLib中对事件源的定义。

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;
};

由定义可以看到,事件源的定义中包含 GMainContext,结构体中还包含成员priority,这表示每个事件源都被指定一个优先级,默认的优先级是G_PRIORITY_DEFAULT,其值为0,当优先级的值小于0表示高优先级,高优先级的事件源优先处理。

GMainLoop 用于表示主事件循环。它由函数g_main_loop_new()函数创建。在为其添加完事件后,调用函数g_main_loop_run()来运行主事件循环。从代码层面上看,GMainLoop就是一个loop,在loop中它,循环会运行GMainContext来持续检查事件源中的事件并对事件进行分发。最终,会有一个事件源中的事件导致函数g_main_loop_quit()被调用,这就意味着程序退出主循环,函数g_main_loop_run()返回。

我们看一下GLib中GMainLoop是如何定义的。

struct _GMainLoop
{
  GMainContext *context;
  gboolean is_running; /* (atomic) */
  gint ref_count;  /* (atomic) */
};
/**
 * g_main_loop_run:
 * @loop: a #GMainLoop
 * 
 * Runs a main loop until g_main_loop_quit() is called on the loop.
 * If this is called for the thread of the loop's #GMainContext,
 * it will process events from the loop, otherwise it will
 * simply wait.
 **/
void 
g_main_loop_run (GMainLoop *loop)
{
......
  g_atomic_int_set (&loop->is_running, TRUE);
  while (g_atomic_int_get (&loop->is_running))
    g_main_context_iterate_unlocked (loop->context, TRUE, TRUE, self);
......
}

 Creating new source types

GMainLoop允许创建和使用不同于GLib中内置的事件源类型的事件源。

新的事件源类型要继承自GSource结构,与GObject中的有继承关系的数据结构的成员的写法类似,新的事件源类型的数据结构的第一个成员是GSource。创建新的事件源类型实例是通过函数g_source_new()实现的。函数的第一个参数GsourceFuncs中的函数决定了新事件源类型的功能。

GLIB_AVAILABLE_IN_ALL
GSource *g_source_new             (GSourceFuncs   *source_funcs,
                                   guint           struct_size);

我们举例看看在GTK中,新事件源类型数据结构的定义情况。

typedef struct _GdkMacosEventSource
{
  GSource     source;
  GdkDisplay *display;
} GdkMacosEventSource;

 新的事件源类型与其所属GMainContext之间有两种交互方式。第一种方式是通过GSourceFuncs结构中的prepare函数设置一个超时,来指定在主事件循环在检查此事件源之前最大的睡眠时间,另一种方式是,事件源通过调用函数g_source_add_poll()将文件描述符添加到集合中,GMainContext会对集合进行检查。

GSourceFuncs结构的定义如下:

/**
 * GSourceDummyMarshal:
 *
 * This is just a placeholder for #GClosureMarshal,
 * which cannot be used here for dependency reasons.
 */
typedef void (*GSourceDummyMarshal) (void);

struct _GSourceFuncs
{
  gboolean (*prepare)  (GSource    *source,
                        gint       *timeout_);/* Can be NULL */
  gboolean (*check)    (GSource    *source);/* Can be NULL */
  gboolean (*dispatch) (GSource    *source,
                        GSourceFunc callback,
                        gpointer    user_data);
  void     (*finalize) (GSource    *source); /* Can be NULL */

  /*< private >*/
  /* For use by g_source_set_closure */
  GSourceFunc     closure_callback;        
  GSourceDummyMarshal closure_marshal; /* Really is of type GClosureMarshal */
};

Customizing the main loop iteration

函数g_main_context_iteration()就能实现单纯的GMainContext迭代。然而,很多时候,我们需要对主事件循环的运行增加精确控制。比如说,GMainLoop在迭代时使用了另外一个主事件循环,此时,你可以调用g_main_context_iteration()的组成函数g_main_context_prepare(), g_main_context_query(), g_main_context_check() and g_main_context_dispatch()。

State of a Main Context

MainContext的状态图如下:

 Main Contexts

What is GMainContext?

GMainContexts是对事件循环的一个通用实现。一个GMaintext会有多个事件源与之相关,每个事件源都可以被认为是一个拥有回调函数的事件,当事件发生时,回调函数就会执行,也可以认为是一个待检测的文件描述符集。例如,超时可以是一个事件,从套接字上首受到的数据也可以是一个事件。

我们来看看GMainContext的定义。

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;
  GMainContextFlags flags;
  GSList *waiters;

  gint ref_count;  /* (atomic) */

  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;
};

一个完整的事件循环会经过一下几个步骤,如上图所示:

  1. 准备事件源。这个步骤用于确定事件源中是否有准备好立即分发事件的事件源

  2.监听事件源。阻塞当前线程,直到事件源中有事件发生。

  3.检查哪个源中有事件发生。

  4.从事件源中分发回调函数。

对于上述的步骤,我们来看看GLib中的实现。

/**
 * g_main_context_iteration:
 * @context: (nullable): a #GMainContext (if %NULL, the global-default
 *   main context will be used)
 * @may_block: whether the call may block.
 *
 * Runs a single iteration for the given main loop. This involves
 * checking to see if any event sources are ready to be processed,
 * then if no events sources are ready and @may_block is %TRUE, waiting
 * for a source to become ready, then dispatching the highest priority
 * events sources that are ready. Otherwise, if @may_block is %FALSE
 * sources are not waited to become ready, only those highest priority
 * events sources will be dispatched (if any), that are ready at this
 * given moment without further waiting.
 *
 * Note that even when @may_block is %TRUE, it is still possible for
 * g_main_context_iteration() to return %FALSE, since the wait may
 * be interrupted for other reasons than an event source becoming ready.
 *
 * Returns: %TRUE if events were dispatched.
 **/
gboolean
g_main_context_iteration (GMainContext *context, gboolean may_block)
{
  gboolean retval;

  if (!context)
    context = g_main_context_default();
  
  LOCK_CONTEXT (context);
  retval = g_main_context_iterate_unlocked (context, may_block, TRUE, G_THREAD_SELF);
  UNLOCK_CONTEXT (context);
  
  return retval;
}


/* HOLDS context lock */
static gboolean
g_main_context_iterate_unlocked (GMainContext *context,
                                 gboolean      block,
                                 gboolean      dispatch,
                                 GThread      *self)
{
......

  g_main_context_prepare_unlocked (context, &max_priority);
  
  while ((nfds = g_main_context_query_unlocked (
            context, max_priority, &timeout, fds,
            allocated_nfds)) > allocated_nfds)
    {
......
    }

......
  g_main_context_poll_unlocked (context, timeout, max_priority, fds, nfds);
  
  some_ready = g_main_context_check_unlocked (context, max_priority, fds, nfds);
  
  if (dispatch)
    g_main_context_dispatch_unlocked (context);
  
......

  return some_ready;
}

GMainContext的核心,其实就是一个poll()循环,prepare函数作为循环的先导,check和disapatch函数作为后续。

static void
g_main_context_poll_unlocked (GMainContext *context,
                              int           timeout,
                              int           priority,
                              GPollFD      *fds,
                              int           n_fds)
{
......

      poll_func = context->poll_func;
       ......
}

在用户没有调用函数g_main_context_set_poll_func设置poll_func时,GLib的默认poll_func为g_poll.

void
g_main_context_set_poll_func (GMainContext *context,
			      GPollFunc     func)
{
  if (!context)
    context = g_main_context_default ();
  
  g_return_if_fail (g_atomic_int_get (&context->ref_count) > 0);

  LOCK_CONTEXT (context);
  
  if (func)
    context->poll_func = func;
  else
    context->poll_func = g_poll;

  UNLOCK_CONTEXT (context);
}


/**
 * g_poll:
 * @fds: file descriptors to poll
 * @nfds: the number of file descriptors in @fds
 * @timeout: amount of time to wait, in milliseconds, or -1 to wait forever
 *
 * Polls @fds, as with the poll() system call, but portably. (On
 * systems that don't have poll(), it is emulated using select().)
 * This is used internally by #GMainContext, but it can be called
 * directly if you need to block until a file descriptor is ready, but
 * don't want to run the full main loop.
 *
 * Each element of @fds is a #GPollFD describing a single file
 * descriptor to poll. The @fd field indicates the file descriptor,
 * and the @events field indicates the events to poll for. On return,
 * the @revents fields will be filled with the events that actually
 * occurred.
 *
 * On POSIX systems, the file descriptors in @fds can be any sort of
 * file descriptor, but the situation is much more complicated on
 * Windows. If you need to use g_poll() in code that has to run on
 * Windows, the easiest solution is to construct all of your
 * #GPollFDs with g_io_channel_win32_make_pollfd().
 *
 * Returns: the number of entries in @fds whose @revents fields
 * were filled in, or 0 if the operation timed out, or -1 on error or
 * if the call was interrupted.
 *
 * Since: 2.20
 **/
gint
g_poll (GPollFD *fds,
	guint    nfds,
	gint     timeout)
{
  return poll ((struct pollfd *)fds, nfds, timeout);
}

  • 27
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值