GtkWidgetブラウザコンポーネント比較その3( マルチスレッド環境のGTK+制御)

久しぶりのブラウザ開発シリーズです。といっても今回はブラウザコンポーネント依存の話ではなく、GTK上で動作するコンポーネント(Gtkオブジェクト)をサブスレッドから操作する方法を紹介します。今回の話はあくまでサブスレッドからの制御についての話なので、ボタンコンポーネント(戻るボタン等)やテキストフィールドコンポーネント(URL指定部等)などGUIコンポーネントの動作を起因に処理を行う場合は、イベント処理で制御する事になると思うので、この話は役に立たないと思います。
何らかのサブスレッドで動く(例えば何らかのネットワークサーバ内の動作や進捗インジケータのアニメーション処理など)ものから操作を行う場合の話になります。

まずGTKライブラリはスレッドセーフに作られていないので、ロックなどで正しくGTK関数の実行を排他的に制御されていないとセグメンテーションフォルトなどが頻発してしまい正しく動作しません。なのでマルチスレッド環境の制御方法としては、

  1. 正しくロック管理を行い、サブスレッドからGTKオブジェクトに操作を行う
    • gdk_threads_init/gdk_threads_enter/gdk_threads_leaveを使うことで実現できます
  2. GTKオブジェクトへの操作はメインスレッドに限定させ、サブスレッドからメインスレッドのアイドル時に行う操作を指定する
    • g_idle_add_fullを使うことで実現できます

のいずれかの方法で実装する必要があります。詳しくはGTK+FAQのページに書かれているので参照してください(両実装のサンプルコードが記載されています)
今回は後者のg_idle_add_fullによるマルチスレッド実装を行ってみます。

ソースコード

#include 

GtkWidget *window;
GtkWidget *web_view;

    // 何らかのスレッド処理
     :
    g_idle_add_full(G_PRIORITY_HIGH, my_useraction, NULL, NULL);
     :

// g_idle_add_fullで指定するアイドル時に実行する関数
gboolean my_useraction(gpointer point) {
	// 例として、別URLに飛ぶ
    gtk_moz_embed_load_url(web_view, "http://www.yahoo.co.jp/");
    return FALSE;
}

// GTKを初期化する関数
int gtk_window_main(int argc, char *argv[]) {
    // GTKの初期化
    if (!g_thread_supported()) {
            g_thread_init(NULL);
    }
    // スレッド処理はコメントアウト
    //gdk_threads_init();

    gtk_init_check(&argc, &argv);
    gtk_set_locale();

    // GTKウインドウを作る
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_moz_embed_set_path("/usr/lib/xulrunner-1.9.2.3/");
    web_view = gtk_moz_embed_new();
    gtk_container_add(GTK_CONTAINER(window), web_view);

    // タイトルを設定
    gtk_window_set_title(GTK_WINDOW(window), "WebViewGTK Demo");
    gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);

    // イベント関数を登録する
    gtk_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);

    gtk_moz_embed_set_chrome_mask(GTK_MOZ_EMBED(web_view),
                    GTK_MOZ_EMBED_FLAG_DEFAULTCHROME);

    gtk_moz_embed_load_url(web_view, "http://www.google.com/");

    gtk_widget_show_all(window);

    // スレッド処理はコメントアウト
    //gdk_threads_enter();

    gtk_main();

    // スレッド処理はコメントアウト
    //gdk_threads_leave();

    return 0;
}

今回はGTK排他制御関数(gdk_threads_init/gdk_threads_enter/gdk_threads_leave)を実行しないようにしています。GTK関連の関数はすべてgtk_main関数のアイドル時に実行するようにしていて、同一スレッドから実行する仕組みになっています。
アイドル時に実行する処理はmy_useraction関数内で定義し、最終行でFALSEを返すようにしています。このコードの注意点としてアイドル関数として指定する関数はFALSEで戻る必要があります。未確認ですがTRUEで返ると操作関数がループして何回も実行されるとの事です。
任意のサブスレッド中からg_idle_add_full関数にてアイドル時にmy_useraction関数を実行するように指示しています。