關於glib的一些知識記錄
_priv->mainloop = g_main_loop_new( _priv->context, FALSE ); \\建立一個GMainloop
_priv->thread = g_thread_new( "thread", threadEntry, NULL ); \\建立一個GThread
2. 執行緒入口
g_main_loop_run( _priv->mainloop ); \\主要實現,執行緒等待GSource事件
3. 執行緒退出與資源釋放
在需要終止執行緒的地方呼叫如下程式碼
g_main_loop_quit( _priv->mainloop ); \\GMainloop退出
g_thread_join( _priv->thread ); \\重要,等待執行緒成功退出,glib內部會對GThread的reference count減1,執行緒資源才會釋放
g_main_loop_unref( _priv->mainloop ); \\釋放之前建立的GMainloop
g_main_context_unref( _priv->context ); \\釋放之前建立的GMainContext
main loop使用模式大致如下:
loop = g_main_loop_new (NULL, TRUE); g_main_loop_run (loop); |
g_main_loop_new建立一個main loop物件,一個main loop物件只能被一個執行緒使用,但一個執行緒可以有多個main loop物件。在GTK+應用中,一個執行緒使用多個main loop的主要用途是實現模態對話方塊,它在gtk_dialog_run函式裡建立一個新的main loop,通過該main loop分發訊息,直到對話方塊關閉為止。
g_main_loop_run則是進入主迴圈,它會一直阻塞在這裡,直到讓它退出為止。有事件時,它就處理事件,沒事件時就睡眠。
g_main_loop_quit則是用於退出主迴圈,相當於Win32下的PostQuitMessage
Glib main loop的最大特點就是支援多事件源,使用非常方便。來自使用者的鍵盤和滑鼠事件、來自系統的定時事件和socket事件等等,還支援一個稱為idle的事件源,其主要用途是實現非同步事件。Main loop的基本組成如下圖所示:
GMainLoop的主要部件是GMainContext,GMainContext可以在多個GMainLoop間共享,但要求這些GMainLoop都在同一個執行緒中執行,前面提到的模態對話方塊就屬於這一類。GMainContext通常由多個GSource組成,GSource是事件源的抽象,任何事件源,只要實現GSource規定的介面,都可以掛到GMainContext中來。
GSource的介面函式有:
1. gboolean (*prepare) (GSource *source, gint *timeout_);進入睡眠之前,在g_main_context_prepare裡,mainloop呼叫所有Source的prepare函式,計算最小的timeout時間,該時間決定下一次睡眠的時間。
2. gboolean (*check) (GSource *source); poll被喚醒後,在g_main_context_check裡,mainloop呼叫所有Source的check函式,檢查是否有Source已經準備好了。如果poll是由於錯誤或者超時等原因喚醒的,就不必進行dispatch了。
3. gboolean (*dispatch) (GSource*source, GSourceFunc callback,gpointer user_data);當有Source準備好了,在g_main_context_dispatch裡,mainloop呼叫所有Source的dispatch函式,去分發訊息。
4. void (*finalize) (GSource *source);在Source被移出時,mainloop呼叫該函式去銷燬Source。
Main loop的工作流程簡圖如下
下面我們看看幾個內建Source的實現機制:
Idle它主要用實現非同步事件,功能類似於Win32下的PostMessage。但它還支援重複執行的特性,根據使用者註冊的回撥函式的返回值而定。
1. g_idle_prepare把超時設定為0,也就是即時喚醒,不進入睡眠狀態。
2. g_idle_check始終返回TRUE,表示準備好了。
3. g_idle_dispatch呼叫使用者註冊的回撥函式。
Timeout它主要用於實現定時器,支援一次定時和重複定時,根據使用者註冊的回撥函式的返回值而定。
1. g_timeout_prepare計算下一次的超時時間。
2. g_timeout_check檢查超時時間是否到了,如果到了就返回TRUE,否則返回FALSE。
3. g_timeout_dispatch呼叫使用者註冊的回撥函式。
執行緒可以向自己的mainloop中增加Source,也可以向其它執行緒的mainloop增加Source。向自己的mainloop中增加Source時,mainloop已經喚醒了,所以不會存在什麼問題。而向其它執行緒的mainloop增加Source時,對方執行緒可能正掛在poll裡睡眠,所以要想法喚醒它,否則Source可能來不及處理。在Linux下,這是通過wake_up_pipe管道實現的,mainloop在poll時,它除了等待所有的Source外,還會等待wake_up_pipe管道。要喚醒poll,呼叫g_main_context_wakeup_unlocked向wake_up_pipe裡寫入字母A就行了。
從文章中摘出來一些,然後自己整理了一下。
GLib 實現了一個功能強大的事件迴圈分發處理機制,GLib 內部實現了三種類型的事件源,分別是 Timeout, Idle, Child Watch。
同時也支援建立自定義的事件源——也就是新增child watch
李先靜老師的FTK就是仿照glib的事件源來實現的
1. Timeout事件源
//mainloop1.c#include<glib.h>
GMainLoop* loop;
gint counter = 10;
gboolean callback(gpointer arg)
{
g_print(".");
if(--counter ==0){
g_print("\n");
//退出迴圈
g_main_loop_quit(loop);
//登出定時器
return FALSE;
}
//定時器繼續執行
return TRUE;
}
int main(int argc, char* argv[])
{
if(g_thread_supported() == 0)
g_thread_init(NULL);
g_print("g_main_loop_new\n");
loop = g_main_loop_new(NULL, FALSE);
//增加一個定時器,100毫秒執行一次callback
g_timeout_add(100,callback,NULL);
g_print("g_main_loop_run\n");
g_main_loop_run(loop);
g_print("g_main_loop_unref\n");
g_main_loop_unref(loop);
return 0;
}
編譯執行:
gcc -g `pkg-config --cflags --libs glib-2.0 gthread-2.0` mainloop1.c -o mainloop1
2. Idle事件源
已經在另一篇部落格中介紹了
3. Child Watch——系統定義的事件源
#include <glib.h>
#include <stdio.h>
#include <strings.h>
GMainLoop* loop;
//當stdin有資料可讀時被GSource呼叫的回撥函式
gboolean callback(GIOChannel *channel)
{
gchar* str;
gsize len;
//從stdin讀取一行字串
g_io_channel_read_line(channel, &str, &len, NULL, NULL);
//去掉回車鍵()
while(len > 0 && (str[len-1] == '\r' || str[len-1] == '\n'))
str[--len]='\0';
//反轉字串
for(;len;len--)
g_print("%c",str[len-1]);
g_print("\n");
//判斷結束符
if(strcasecmp(str, "q") == 0){
g_main_loop_quit(loop);
}
g_free(str);
}
void add_source(GMainContext *context)
{
GIOChannel* channel;
GSource* source;
//這裡我們監視stdin是否可讀, stdin的fd預設等於1
channel = g_io_channel_unix_new(1);
//g_io_create_watch建立一個預設的io監視作用的GSource,下次再研究自定義GSource。引數G_IO_IN表示監視stdin的讀取狀態。
source = g_io_create_watch(channel, G_IO_IN);
g_io_channel_unref(channel);
//設定stdin可讀的時候呼叫的回撥函式
g_source_set_callback(source, (GSourceFunc)callback, channel, NULL);
//把GSource附加到GMainContext
g_source_attach(source, context);
g_source_unref(source);
}
int main(int argc, char* argv[])
{
GMainContext *context;
if(g_thread_supported() == 0)
g_thread_init(NULL);
//新建一個GMainContext
context = g_main_context_new();
//然後把GSource附到這個Context上
add_source(context);
//把Context賦給GMainLoop
loop = g_main_loop_new(context, FALSE);
g_print("input string('q' to quit)\n");
g_main_loop_run(loop);
g_main_loop_unref(loop);
//Context用完計數器減1
g_main_context_unref(context);
return 0;
}
4. Child Watch——自定義事件源
GSource * g_source_new(GSourceFuncs * source_funcs, guint struct_size);
這個函式用於建立一個自定義事件源,新的事件源可以使用 g_source_attach() 函式加入到主迴圈上下文中。
source_funcs : 包含用於實現事件行為的函式的結構
struct_size : 建立的 GSource 結構大小,不能小於 sizeof(GSource)
返回值 : 返回新建立的 GSource
建立一個新的事件源包含用於實現事件行為的函式的結構體。
prepare : 設定檢查事件時間超時。如果返回 TRUE, check 會立刻被呼叫;如果返回 FALSE 並設定了 timeout , timeout 時間後 check 會被呼叫。
check : 檢查事件是否準備完畢。返回 TRUE 為準備完畢, dispatch 會被立刻呼叫;返回 FALSE 不呼叫 dispatch,進入下一次事件迴圈。
dispatch : 分發事件。返回 TRUE 將繼續下一次操作迴圈;返回 FALSE 中止本事件源的事件迴圈。
finalize : 當事件源被移除時被呼叫。
gboolean source_prepare_cb(GSource * source,
gint * timeout)
{
g_printf("prepare\n");
*timeout = 1000;
return FALSE;
}
gboolean source_check_cb(GSource * source)
{
g_printf("check\n");
return TRUE;
}
gboolean source_dispatch_cb(GSource * source,
GSourceFunc callback, gpointer data)
{
g_printf("dispatch\n");
return TRUE;
}
void source_finalize_cb(GSource * source)
{
g_printf("finalize\n");
}
int main(int argc, char * argv[])
{
GMainLoop * mainloop;
GMainContext * maincontext;
GSource * source;
GSourceFuncs sourcefuncs;
sourcefuncs.prepare = source_prepare_cb;
sourcefuncs.check = source_check_cb;
sourcefuncs.dispatch = source_dispatch_cb;
sourcefuncs.finalize = source_finalize_cb;
mainloop = g_main_loop_new(NULL, FALSE);
maincontext = g_main_loop_get_context(mainloop);
source = g_source_new(&sourcefuncs, sizeof(GSource));
g_source_attach(source, maincontext);
g_main_loop_run(mainloop);
return 0;
}
注意prepare 中會返回一個超時時間
g_main_loop_new
按著glib的文件順序,先來看看事件迴圈吧。
從最簡單的例子開始:
//mainloop0.c
#include<glib.h>
GMainLoop* loop;
int main(int argc, char* argv[])
{
//g_thread_init是必需的,GMainLoop需要gthread庫的支援。
if(g_thread_supported() == 0)
g_thread_init(NULL);
//建立一個迴圈體,先不管引數的意思。
g_print("g_main_loop_new/n");
loop = g_main_loop_new(NULL, FALSE);
//讓這個迴圈體跑起來
g_print("g_main_loop_run/n");
g_main_loop_run(loop);
//迴圈執行完成後,計數器減一
//glib的很多結構型別和c++的智慧指標相似,擁有一個計數器
//當計數器為0時,自動釋放資源。
g_print("g_main_loop_unref/n");
g_main_loop_unref(loop);
return 0;
}
好了,現在編譯:
gcc -g `pkg-config --cflags --libs glib-2.0 gthread-2.0` mainloop0.c -o mainloop0
然後執行:
./mainloop0
你會發現程式會在g_main_loop_run函式阻塞,這就是glib main loop了,如果沒有人通知它退出,它是不會退出的。
通知迴圈退出的函式是g_main_loop_quit。
怎麼通知呢?主執行緒被g_main_loop_run阻塞了,沒辦法執行quit。本來我準備開一個執行緒,sleep一秒鐘,然後呼叫g_main_loop_quit。不過一想我們都在學習高精尖武器了,還用土槍土炮幹啥。使用glib的定時器吧~
//mainloop1.c
#include<glib.h>
GMainLoop* loop;
gint counter = 10;
gboolean callback(gpointer arg)
{
g_print(".");
if(--counter ==0){
g_print("/n");
//退出迴圈
g_main_loop_quit(loop);
//登出定時器
return FALSE;
}
//定時器繼續執行
return TRUE;
}
int main(int argc, char* argv[])
{
if(g_thread_supported() == 0)
g_thread_init(NULL);
g_print("g_main_loop_new/n");
loop = g_main_loop_new(NULL, FALSE);
//增加一個定時器,100毫秒執行一次callback
g_timeout_add(100,callback,NULL);
g_print("g_main_loop_run/n");
g_main_loop_run(loop);
g_print("g_main_loop_unref/n");
g_main_loop_unref(loop);
return 0;
}
編譯執行:
gcc -g `pkg-config --cflags --libs glib-2.0 gthread-2.0` mainloop1.c -o mainloop1
./mainloop1
Yeah!一秒鐘後,程式正常退出了!定時器好簡單!
今天到此為止。最後思考一個問題,glib的main loop主要提供給gtk使用,是gtk介面事件迴圈的基礎,這是無可非議的。但是,在別的地方,比如我們普通的console、service程式中,有必要用main loop麼?main loop還能夠應用在哪些場合?
-----------------------
回到前一天的問題,除了互動性介面程式,還有哪些地方適合使用glib的event loop呢?我認為答案應該是,所有需要非同步操作的地方都可以用event loop。像檔案、管道、裝置、socket、timer、idle和其他自定義的事件都可以產生event.
要讓GMainLoop能夠處理這些型別的event,首先就必須把它們加到GMainLoop去。
首先我們需要了解event loop的這三個基本結構:GMainLoop, GMainContext和GSource。
它們之間的關係是這樣的:
GMainLoop -> GMainContext -> {GSource1, GSource2, GSource3......}
每個GmainLoop都包含一個GMainContext成員,而這個GMainContext成員可以裝各種各樣的GSource,GSource則是具體的各種Event處理邏輯了。在這裡,可以把GMainContext理解為GSource的容器。(不過它的用處不只是裝GSource)
建立GMainLoop使用函式g_main_loop_new, 它的第一個引數就是需要關聯的GMainContext,如果這個值為空,程式會分配一個預設的Context給GMainLoop。
把GSource加到GMainContext呢,則使用函式g_source_attach。
接下來看這個例子,它的作用是從stdin讀取字串,然後反轉字串並輸出到螢幕。
//mainloop2.c
#include <glib.h>
#include <stdio.h>
#include <strings.h>
GMainLoop* loop;
//當stdin有資料可讀時被GSource呼叫的回撥函式
gboolean callback(GIOChannel *channel)
{
gchar* str;
gsize len;
//從stdin讀取一行字串
g_io_channel_read_line(channel, &str, &len, NULL, NULL);
//去掉回車鍵()
while(len > 0 && (str[len-1] == '/r' || str[len-1] == '/n'))
str[--len]='/0';
//反轉字串
for(;len;len--)
g_print("%c",str[len-1]);
g_print("/n");
//判斷結束符
if(strcasecmp(str, "q") == 0){
g_main_loop_quit(loop);
}
g_free(str);
}
void add_source(GMainContext *context)
{
GIOChannel* channel;
GSource* source;
//這裡我們監視stdin是否可讀, stdin的fd預設等於1
channel = g_io_channel_unix_new(1);
//g_io_create_watch建立一個預設的io監視作用的GSource,下次再研究自定義GSource。引數G_IO_IN表示監視stdin的讀取狀態。
source = g_io_create_watch(channel, G_IO_IN);
g_io_channel_unref(channel);
//設定stdin可讀的時候呼叫的回撥函式
g_source_set_callback(source, (GSourceFunc)callback, channel, NULL);
//把GSource附加到GMainContext
g_source_attach(source, context);
g_source_unref(source);
}
int main(int argc, char* argv[])
{
GMainContext *context;
if(g_thread_supported() == 0)
g_thread_init(NULL);
//新建一個GMainContext
context = g_main_context_new();
//然後把GSource附到這個Context上
add_source(context);
//把Context賦給GMainLoop
loop = g_main_loop_new(context, FALSE);
g_print("input string('q' to quit)/n");
g_main_loop_run(loop);
g_main_loop_unref(loop);
//Context用完計數器減1
g_main_context_unref(context);
return 0;
}
1. 之前提到過,GSource和被管理的檔案的對應關係,不是 1對1,而是 1對n,這個n,甚至可以是0。這個的意思就是說,一個GSource,可以對應(管理)一個檔案描述符,也可以同時對應(管理)多個檔案描述符,也可以一個都不管理。
glib中已經提供了幾個GSource的子類,其中的g_timeout_source_new(guint interval)和g_idle_source_new(void)這兩個函式構造出的GSource子類,就並不使用檔案描述符號。
GTimeoutSource,只需要儲存設定的間隔時間,在poll輪循的prepare和check階段,會去檢查設定的這個間隔時間是否到達,如果到達設定的時間,則它的dispatch函式會被呼叫。
g_idle_source_new(void)其實都沒有繼承GSource,它也不需要和檔案繫結,只不過它的優先順序比較低,只有在poll輪詢的空閒階段,它的dispatch函式會被呼叫。而它的prepare和check函式,始終都是返回的TRUE。
2. GMainLoop怎樣根據GSource的優先順序進行排程?
在g_main_context_prepare()的時候,會從所有的GSource中找出最大的一個優先順序,然後在g_main_context_query()的時候,只會把等於這個優先順序的GSource對應的GPollFD新增到poll的監控集合中。基於這個優先順序制度,GMainLoop就可以對GSource進行一個排程。以idle source為例,如果同時有一個GTimeoutSource和idle source,因為idle source的優先順序更低,所有GMainLoop就不會把idle source新增到poll輪詢的監控集合中,也就是說,直到GTimeoutSource從GMainLoop中移除,idle source的callback函式,才有機會被呼叫。
做Linux程式開發有一段時間了,也使用過好幾個UI庫,包括gtk,qt,還有clutter。其中感覺最神祕的,就是所謂的“主事件迴圈",在qt中,就是QApplication,gtk中是gtk_main(),clutter中則是clutter_main()。這些事件迴圈物件,都被封裝的很“嚴密",使用的時候,程式碼都很簡單。而我們在編寫應用程式的過程中,通常也只需要過載widget的event處理函式(或者是處理event對應的訊號),至於event是怎樣產生和傳遞的,這就是個謎。
最近時間比較充裕,仔細研究了一下事件迴圈,參考的程式碼是glib中的GMainLoop。gtk_main()和clutter_main(),都是基於GMainLoop的。另外,其實事件迴圈的概念,也不僅僅使用在UI程式設計中,在網路程式設計中,同樣大量的使用,可以這樣說,event loop,是程式設計模型中,最基本的一個概念。可惜在大學教材中,從來沒有看到過這個概念,玩微控制器的時候,也用不到這個概念,只有在有作業系統的環境下,才會有event loop。
event loog的程式碼基礎,還要用到一個概念--I/O的多路複用。目前常用的api介面,有3個,select,poll以及epoll。glib是一個跨平臺的庫,在linux上,使用的是poll函式,在window上,使用的是select。而epoll這個介面,在linux2.6中才正式推出,它的效率,比前兩者更高,在網路程式設計中大量使用。而本質上,這三個函式,其實是相同的。
如果對I/O多路複用還不瞭解,請先自行google學習。下面,僅僅給出一個使用poll介面的程式碼模型片段。
[cpp] view plain copy print?- ...
- struct pollfd fds[2];
- int timeout_msecs = 500;
- int ret;
- int i;
- /* Open STREAMS device. */
- fds[0].fd = open("/dev/dev0", ...);
- fds[1].fd = open("/dev/dev1", ...);
- fds[0].events = POLLOUT | POLLWRBAND;
- fds[1].events = POLLOUT | POLLWRBAND;
- while(1) {
- ret = poll(fds, 2, timeout_msecs);
- if (ret > 0) {
- /* An event on one of the fds has occurred. */
- for (i=0; i<2; i++) {
- if (fds[i].revents & POLLWRBAND) {
- /* Priority data may be written on device number i. */
- ...
- }
- if (fds[i].revents & POLLOUT) {
- /* Data may be written on device number i. */
- ...
- }
- if (fds[i].revents & POLLHUP) {
- /* A hangup has occurred on device number i. */
- ...
- }
- }
- }
- }
- ...
上面這個程式碼,我們可以把它拆分成3部分:
1. 準備要檢測的檔案集合(不是簡單的準備“檔案描述符"的集合,而是準備struct pollfd結構體的集合。這就包括了檔案描述符,以及希望監控的事件,如可讀/可寫/或可執行其他操作等)。
2. 執行poll,等待事件發生(檔案描述符對應的檔案可讀/可寫/或可執行其他操作等)或者是函式超時返回。
3. 遍歷檔案集合(struct pollfd結構體的集合),判斷具體是哪些檔案有“事件"發生,並且進一步判斷是何種“事件"。然後,根據需求,執行對應的操作(上面的程式碼中,用...表示的對應操作)。
其中2和3對應的程式碼,都放在一個while迴圈中。而在3中所謂的“對應的操作",還可以包括一種“退出"操作,這樣的話,就可以從while迴圈中退出,這樣的話,整個程序也有機會正常結束。
再次提醒一下,請先把上面這段程式碼看懂,最好是有過實際的使用經驗,這樣更有助於理解。
下面開始討論重點。這段程式碼僅僅是演示,所以它很簡單。但是,從另外一個角度來看,這個程式碼片段又很死板,尤其是對於新手或者是沒有I/O多路複用實際使用經驗的朋友來說,很容易被這段程式碼模型“框住"。它還能變得更靈活嗎?怎樣才能變得更靈活?詳細解釋之前,先提幾個小問題。
1. 前面的程式碼,僅打開了2個檔案,並且傳遞給poll函式。如果,在程式執行過程中,想動態的增加或者刪除poll函式監控的檔案,怎麼辦?
2. 前面的程式碼,設定的超時時間,是固定的。假設,某個時刻,有100個檔案需要被監控,而針對這100個不同的檔案,每個檔案期望設定的超時時間都不一樣,怎麼辦?
3. 前面的程式碼,當poll函式返回,對檔案集合進行遍歷的時候,是逐個進行判斷並且執行“對應的操作"。如果,有100個檔案被監控,當poll返回時,這100個檔案,都滿足條件,可以進行“對應的操作",其中的50個檔案的“對應的操作"很耗時間,但是並不是這麼緊急(可以稍後再處理,比如等到下一輪poll返回時再處理),而另外50個檔案的“對應的操作"需要立即執行,並且很快(在下一次poll的時候)又會有新的事件發生並且滿足判斷時的條件,怎麼辦?
對第1個問題,可以想到,需要對所有的檔案(struct pollfd)做一個統一的管理,需要有新增和刪除檔案的功能。用面向物件的思想來看,這就是一個類,暫且叫做類A。
對第2個問題,可以想到,還需要對每一個被監控的檔案(struct pollfd),做更多的控制。也可以用一個類來包裝被監控的檔案,對這個檔案進行管理,在該物件中,包含了struct pollfd結構體,該類還可以提供對應的檔案所期望的超時時間。暫且叫做類B。
對第3個問題,可以考慮為每一個被監控的檔案設定一個優先順序,然後就可以根據優先順序優先執行更“緊急"的“對應的操作"。這個優先順序資訊,也可以儲存在類B中。設計出了類B之後,類A就不再是直接統一管理檔案了,而是變成統一管理類B,可以看成是類B的一個容器類。
有了這3個解答之後,就可以對這個程式碼片段添油加醋,重新組裝,讓它變得更靈活了。glib中的GMainLoop,做的就是這樣的事情,而且,它做的事情,除了這3個解答中描述的內容外,還有更讓人“吃驚的驚喜"。
:-),這裡又要提醒一下了,下面將對GMainLoop進行描述,所以,最好是先使用一下GMainLoop,包括其中的g_timeout_source_new(guint interval),g_idle_source_new(void)以及g_child_watch_source_new(GPid pid)。順便再強調一下,學習程式設計的最好的辦法,就是看程式碼,而且是看高質量的程式碼。
後面的講解,主要是從原理上來介紹GMainLoop的實現機制,並不是程式碼的情景分析。程式碼的詳細閱讀,還是需要自己老老實實的去實踐的。後面的這些介紹,只是為了幫助大家更容易的理解原始碼。
glib的主事件迴圈框架,由3個類來實現,GMainLoop,GMainContext和GSource,其中的GMainLoop僅僅是GMainContext的一個外殼,最重要的,還是GMainContext和GSource。GMainContext就相當於前面提到的類A,而GSource就相當於前面提到的類B。從原理上講,g_main_loop_run(GMainLoop *loop)這個函式的內部實現,和前面程式碼片段中的while迴圈,是一致的。(還有一點要說明的,在多執行緒的環境下,GMainLoop的程式碼實現顯得比較複雜,為了學習起來更容易些,可以先不考慮GMainLoop中執行緒相關的程式碼,這樣的話,整體結構就和前面的程式碼片段是一致的。後面的講解以及程式碼片段,都略去了執行緒相關的程式碼,這並不影響對event loop的學習和理解)。
1.GSource----GSource相當於前面提到的類B,它裡面會儲存優先順序資訊,同時,GSource要管理對應的檔案(儲存struct pollfd結構體的指標,而且是以連結串列的形式儲存),而且,GSource和被管理的檔案的對應關係,不是 1對1,而是 1對n,這個n,甚至可以是0(這就是一個“吃驚的驚喜",後面會有更詳細的解釋)。GSource還必須提供3個重要的函式(從面向物件的角度看,GSource是一個抽象類,而且有三個重要的純虛擬函式,需要子類來具體實現),這3個函式就是:
gboolean (*prepare) (GSource *source, gint *timeout_);
gboolean (*check) (GSource *source);
gboolean (*dispatch) (GSource *source, GSourceFunc callback, gpointer user_data);
再看一下前面程式碼片段中的3部分,這個prepare函式,就是要在第一部分被呼叫的,check和dispathch函式,就是在第3部分被呼叫的。有一點區別是,prepare函式,也要放到while迴圈中,而不是在迴圈之外(因為要動態的增加或者刪除poll函式監控的檔案)。
prepare函式,會在執行poll之前被呼叫。該GSource中的struct pollfd是否希望被poll函式監控,就由prepare函式的返回值來決定,同時,該GSource希望的超時時間,也由引數timeout_返回。
check函式,在執行poll之後被呼叫。該GSource中的struct pollfd是否有事件發生,就由check函式的返回值來描述(在check函式中可以檢測struct pollfd結構體中的返回資訊)。
dispatch函式,在執行poll和check函式之後被呼叫,並且,僅當對應的check函式返回true的時候,對應的dispatch函式才會被呼叫,dispatch函式,就相當於“對應的操作"。
2.GMainContext----GMainContext是GSource的容器,GSource可以新增到GMainContext裡面(間接的就把GSource中的struct pollfd也新增到GMainContext裡面了),GSource也可以從GMainContext中移除(間接的就把GSource中的struct pollfd從GMainContext中移除了)。GMainContext可以遍歷GSource,自然就有機會呼叫每個GSource的prepare/check/dispatch函式,可以根據每個GSource的prepare函式的返回值來決定,是否要在poll函式中,監控該GSource管理的檔案。當然可以根據GSource的優先順序進行排序。當poll返回後,可以根據每個GSource的check函式的返回值來決定是否需要呼叫對應的dispatch函式。
下面給出關鍵的程式碼片段,其中的g_main_context_iterate()函式,就相當於前面程式碼片段中的迴圈體中要做的動作。迴圈的推出,則是靠loop->is_running這個標記變數來標識的。
[cpp] view plain copy print?- void g_main_loop_run (GMainLoop *loop)
- {
- GThread *self = G_THREAD_SELF;
- g_return_if_fail (loop != NULL);
- g_return_if_fail (g_atomic_int_get (&loop->ref_count) > 0);
- g_atomic_int_inc (&loop->ref_count);
- loop->is_running = TRUE;
- while (loop->is_running)
- g_main_context_iterate (loop->context, TRUE, TRUE, self);
- UNLOCK_CONTEXT (loop->context);
- g_main_loop_unref (loop);
- }
- static gboolean g_main_context_iterate(GMainContext *context, gboolean block,
- gboolean dispatch, GThread *self) {
- gint max_priority;
- gint timeout;