2021年11月28日 星期日

glib基礎

 GUI應用程序都是事件驅動的。這些事件大部分都來自於用戶,比如鍵盤事件、鼠標事件或筆點事件。還有一些事件來自於系統內部,比如定時事件、socket事件和其它文件事件等等。在沒有任何事件的情況下,應用程序處於睡眠狀態。


因為這種事件驅動機制,GUI應用程序都毫無例外的需要一個主循環(main loop)。主循環(main loop)控制應用程序什麼時候進入睡眠狀態,什麼時候被喚醒。主循環實現得好,應用程序才能工作正常又省電。


Win32 GUI應用程序的主循環是我們比較熟悉的,其大致如下:

       // Main message loop:

       while (GetMessage(&msg, NULL, 0, 0))

       {

              if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))

              {

                     TranslateMessage(&msg);

                     DispatchMessage(&msg);

              }

       }


在這個主循環中,它不斷的從消息隊列中提取消息,然後分發給消息的目標(通常是窗口),直到GetMessage返回FALSE(收到WM_QUIT消息,一般調用PostQuitMessage)為止,如果隊列中沒有消息,應用程序就進入睡眠狀態。這種方法簡單明了,缺陷也是明顯的,它只能掛在消息隊列上,而不能同時掛在多個事件源上(如管道和socket等)。要掛在多個事件源上,需要使用其它方式,比如用WaitForMultipleObjects,那就比較麻煩了。


而在GTK+應用程序中,其主循環(main loop)更加簡單,但是非常的不明了:

gtk_main ();


gtk_main主要是對glib的main loop的包裝,基本上分為三步:

1.初始化

g_main_loop_new( _priv->context, FALSE );    \\建立一個GMainloop

g_thread_new( "thread", threadEntry, NULL );    \\建立一個GThread


2. 執行緒入口

g_main_loop_run( _priv->mainloop );    \\進入主迴圈,它會一直阻塞在這裡,直到讓它退出為止。執行緒等待GSource事件,有事件時,它就處理事件,沒事件時就睡眠。


3. 執行緒退出與資源釋放

在需要終止執行緒的地方呼叫如下程式碼

g_main_loop_quit( _priv->mainloop );    \\GMainloop退出主迴圈,相當於Win32下的PostQuitMessage函式

g_thread_join( _priv->thread );    \\重要,等待執行緒成功退出,glib內部會對GThread的reference count減1,執行緒資源才會釋放

g_main_loop_unref( _priv->mainloop );     \\釋放之前建立的GMainloop

g_main_context_unref( _priv->context );    \\釋放之前建立的GMainContext



Glib main loop的最大特點就是支持多事件源,使用非常方便。來自用戶的鍵盤和鼠標事件、來自系統的定時事件和socket事件等等,還支持一個稱為idle的事件源,其主要用途是實現異步事件。 Main loop的基本組成如下圖所示:

http://hi.csdn.net/attachment/201110/13/0_1318477410T9jo.gif



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函數。


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的工作流程簡圖如下:

http://hi.csdn.net/attachment/201110/13/0_1318477505zT28.gif



內置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就行了。



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是怎樣產生和傳遞的,這就是個謎。


event loog的程式碼基礎,還要用到一個概念--I/O的多路複用。目前常用的api介面,有3個,select,poll以及epoll。 glib是一個跨平臺的庫,在linux上,使用的是poll函式,在window上,使用的是select。而epoll這個介面,在linux2.6中才正式推出,它的效率,比前兩者更高,在網路程式設計中大量使用。而本質上,這三個函式,其實是相同的。


REF

https://blog.csdn.net/absurd/article/details/1531927

https://www.itread01.com/content/1549988130.html


沒有留言:

張貼留言