1. 程式人生 > >GTK搭建數採程式的多執行緒解決方法

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 

http://developer.gnome.org/glib/2.30/glib-IO-Channels.html

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經過商業運作的好處吧。