GTK搭建數採程式的多執行緒解決方法
對一個數據採集系統而言,通常需要一個圖形互動介面,同時底層的採集過程也在連續進行並不斷重新整理,並不斷顯示和更新圖形介面(比如重新整理波形圖)。
通常,圖形介面GUI本身即是一個迴圈,介面上的控制元件產生訊號,介面主迴圈MainLoop能夠響應這些訊號並執行相應的回撥函式。
在LabWindows/CVI以及C#.NET裡,這個問題很好解決,使用一個Timer控制元件即可。Timer控制元件可以實時產生一個TICK訊號/事件,比如CVI裡就是EVENT_TIMER_TICK。我們只需要把回撥函式和這個TICK事件連線起來,在圖形介面主迴圈執行過程中會迴圈響應這個Timer控制元件發出的TICK,並執行callback。callback可以是採集函式、波形顯示函式等等。這樣就做到了底層採集過程和主介面同時進行和重新整理。T
但是,gtk沒有類似上面提到的Timer這樣功能的部件。glib中只有Gtimer相關的函式,但只是一個計時器,並不能實時迴圈產生訊號。GtkMain本身即是一個迴圈。對於採集過程而言,我們也希望能夠迴圈執行資料採集、波形顯示和重新整理過程,但是主介面迴圈和採集過程迴圈怎麼同時執行呢?
在stackoverflow上有關於這個問題的很好的回答:http://stackoverflow.com/questions/8826523/gtk-main-and-unix-sockets點選開啟連結
One solution is to integrate your events into the event loop of Gtk+.
You can make Gtk+ watch/select()
your
sockets and call a specific function when their state changes (data readable). See the section "Creating new source types" onhttp://developer.gnome.org/glib/2.30/glib-The-Main-Event-Loop.html
Another solution would be to use Gtk+ networking functionality.
Typically you don't want to do something so special with the sockets that it is not easily wrapable with Glib IO Channels. See
A third solution is to start a second thread that handles your networking, e.g. with posix threads or Gtk+ threading functionality.
Separating GUI from the worker part of your application is in general a good idea. However for a chat application, it probably does not give any benefit over the other solutions. Seehttp://developer.gnome.org/glib/2.30/glib-Threads.html
即方法有三種:第一是在gtk loop的event中整合和產生一個訊號,第二個是使用包裝好的網路函式,第三就是多執行緒。對於不想深入研究gtk內部機制的程式設計小白來說,改寫gtkmainloop和event有難度。使用多執行緒,簡單明瞭,很好理解。
gtk主介面是一個執行緒,新建一個執行緒用於採集,新執行緒中使用迴圈即可實現採集和圖形介面的實時重新整理。
下面是程式碼,這個小程式主視窗是一個波形圖控制元件xyplot(來源:http://sourceforge.net/projects/giw/)和一個toggle button。當toggle button被按下時,程式迴圈產生模擬波形。
#include <gtk/gtk.h>
#include <math.h>
#include "giwxyplot.h"
GtkWidget *window;
GtkWidget *xyplot;
GtkWidget *hbox;
GtkWidget *togglebutton;
XYPlotData data1;
gint count;
static gint draw_thread()
{
while(1)
{
gdk_threads_enter();
if (gtk_toggle_button_get_active(togglebutton))
{
giw_xyplot_remove_data(GIW_XYPLOT(xyplot),&data1);
count=count+1;
int loop;
for(loop=0; loop<1000; loop++)
{
data1.y[loop]=sin(data1.x[loop]+count);
}
//gtk_widget_queue_draw(GTK_WIDGET(xyplot));
printf("%d\n",count);
giw_xyplot_add_data(GIW_XYPLOT(xyplot),&data1);
gtk_widget_queue_draw(GTK_WIDGET(xyplot));
}
gdk_threads_leave();
}
return TRUE;
}
static void gtk_window_init(int argc,char **argv)
{
if((!g_thread_supported()))
{
g_thread_init(NULL);
}
gdk_threads_init();
gtk_init(&argc,&argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "GiwXYPlot Simple Example");
gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_exit), NULL);
gtk_container_border_width (GTK_CONTAINER (window), 10);
hbox=gtk_hbox_new(FALSE,0);
gtk_container_add(GTK_CONTAINER(window),hbox);
// Creating the xyplot
xyplot=giw_xyplot_new();
gtk_container_add(GTK_CONTAINER(hbox), xyplot);
togglebutton=gtk_toggle_button_new_with_label("Status");
gtk_container_add(GTK_CONTAINER(hbox), togglebutton);
gtk_widget_show_all(window);
}
int main (int argc, char *argv[])
{
gint count=0;
int loop;
data1.x=(double*) g_malloc(sizeof(double[1000]));
data1.y=(double*) g_malloc(sizeof(double[1000]));
for(loop=0; loop<1000; loop++)
{
data1.x[loop]=((double)loop)/20.0;
data1.y[loop]=cos(50*data1.x[loop]+count);
}
data1.size=100;
data1.style=GIW_XYPLOT_DATA_LINES;
data1.p_width=4;
data1.l_width=0;
data1.p_color.red=0;
data1.p_color.green=0;
data1.p_color.blue=0;
data1.l_color.red=0;
data1.l_color.green=0;
data1.l_color.blue=0;
data1.line_style=GDK_LINE_SOLID;
gtk_window_init(argc,argv);
giw_xyplot_add_data(GIW_XYPLOT(xyplot),&data1);
g_thread_create((GThreadFunc)draw_thread,NULL,FALSE,NULL);
gdk_threads_enter();
gtk_main();
gdk_threads_leave();
return 0;
}
初始化程序需要使用 g_thread_init (NULL)及 gdk_threads_init ();
由於新建的執行緒還要對圖形介面進行操作,即兩個執行緒要去操控同一個圖形面板,因此需要用到這一對函式:gdk_threads_enter()gdk_threads_leave(),保證多執行緒之間資源的安全呼叫。按照reference的說法,即“Only one thread at a time can be in such a critial section.”,上面這一對函式即對這個臨界區域的操作,即在每個時間點上,都只有一個執行緒在操作資源,避免了兩個執行緒的衝突。因此可以看出,所謂多執行緒,實質上仍然是兩個執行緒輪流切換。
需要指出,多執行緒只能用於X11終端。reference原話:“Unfortunately the above holds with the X11 backend only. With the Win32 backend, GDK calls should not be attempted from multiple threads at all.”
最後介紹一下上面用到過的波形顯示構件,來源於GTK Instrumentation Widgets,基於gtk2.x,沒有用cairo作為繪圖引擎。
算下來馬馬虎虎學習gtk其實也一個月了,最痛苦的就是版本問題,gtk2和gtk3之間確實有很大的不同,而gtk2.x之間也同樣存在很多差異。之前也接觸過一點點Qt,不管從文件豐富性,還是本身的穩定性和延續性上來講,Qt都有明顯的優勢。也許這就是Qt經過商業運作的好處吧。