3

GTK と C を使用して、ボタンを使用して (別のスレッドで) 長い計算を開始/停止するにはどうすればよいですか? 私はまさにそれを行う作業コードを持っていますが、それが合理的な方法 (つまり、「正しい」) であるという確信はほとんどありません。

ラベルが「開始」から「停止」に切り替わる単一のボタンがあります。スレッドを格納するためのグローバルな pthread_t 変数もあります。私のアプローチは、スレッドが現在実行されているかどうかを示すグローバルなブール値のような「アイドル」フラグの値に応じて、ボタンのクリックされたシグナルハンドラーを介してスレッドを起動またはキャンセルすることです。

大規模なプログラムに適応するコードを簡単に理解できるように、うまく設計された最小限のテスト ケースが必要でした。この質問はPython&PyGTK: Stop while on button clickに非常に似ていますが、その質問は私が知らない python にあります。

私のコード --- 以下に掲載 --- は機能しているように見えますが、開始/停止ボタンを数回すばやく連続してクリックするだけでシステムを簡単にひざまずかせることができるため、自信がありません。

他の人がこれを(独立して)どのように解決するか、彼らのアプローチが私のアプローチとどのように比較されるか、そしてそれが実際にまともな方法である場合、私自身のアプローチのコードレビューにも興味があります。

#include <gtk/gtk.h>
#include <pthread.h>

/* suppress unused variable warnings */
#define UNUSED(x) (void)(x)

typedef struct _Data {
    GtkWidget *window1,
              *button1;
    gint idle;
    pthread_t calcthread;
} Data;

static Data *data;

void *calcfunc(void *arg) {
    int i;
    UNUSED(arg);

    data->idle=FALSE;
    gtk_button_set_label(GTK_BUTTON(data->button1),"Stop");

    /* This is intended to simulated a long calculation that may finish.
       Adjust the limit as needed */
    for(i=1;i<2e9;++i) {
    }

    data->idle=TRUE;
    pthread_exit(NULL);
}

/* this is our click event handler.... it suppose to start or stop 
   the "calcthread" depending on the value of the "idle" flag */
void on_button1_clicked(GtkWidget *widget, Data *ldata) {
    int ret;
    UNUSED(widget);
    UNUSED(ldata);

    if ( data->idle==TRUE ) {
        printf("idle.. starting thread\n");
        ret=pthread_create( &data->calcthread, NULL, calcfunc, NULL);
        if ( ret !=0 ) {
            g_error("ERROR: could not create thread\n");
        }
    } else {
        printf("not idle... canceling thread...");
        ret= pthread_cancel( data->calcthread );
        if ( ret != 0 ) {
            g_error("ERROR: could not cancel thread\n");
        } else {
            printf("canceled\n");
        }
        data->idle=TRUE;
        gtk_button_set_label(GTK_BUTTON(data->button1),"start");
    }
}

/* just defines our setup */
int main (int argc, char *argv[]) {

    g_thread_init(NULL);
    gdk_threads_init();
    gdk_threads_enter();

    gtk_init(&argc, &argv);

    data=g_slice_new0(Data);
    data->idle=TRUE; /* initial state */

    printf("idle is %d\n",data->idle);

    /* add widgets and objects to our structure */

    data->window1=gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_default_size(GTK_WINDOW(data->window1),250,250);
    data->button1=gtk_button_new_with_label("Start");
    gtk_container_add(GTK_CONTAINER(data->window1),GTK_WIDGET(data->button1));

    gtk_signal_connect(GTK_OBJECT(data->window1), "delete-event",
                       gtk_main_quit, NULL);
    gtk_signal_connect(GTK_OBJECT(data->button1), "clicked",
                           G_CALLBACK(on_button1_clicked), NULL);

    gtk_widget_show_all(GTK_WIDGET(data->window1));

    gtk_main();

    /* Don't forget to free the memory! */
    g_slice_free(Data, data);

    gdk_threads_leave();

    return 0;
}
4

3 に答える 3

3

セカンダリ スレッドから GTK 関数を呼び出しているため、呼び出しをラップする必要があります。

gtk_button_set_label(GTK_BUTTON(data->button1),"Stop");

gdk_threads_enter/でgdk_threads_leave呼び出します。ただし、1 つのスレッドからのみ GTK 関数を呼び出すことをお勧めします。最も簡単な方法は、アイドル関数を使用g_idle_addすることです。これは、メイン スレッドから呼び出されるためです。ただし、この場合は、呼び出しをgtk_button_set_labelfromcalcfuncに移動するだけon_button1_clickedです。

また、ボタンのクリックが速すぎる競合状態を解決するためdata->idle = FALSEに、ハンドラーを設定する必要があります。on_button1_clicked

これを行う別の方法は、スレッドを使用せずに、長い操作中に GTK メイン ループを実行することです。ループでは、Gtk イベント ループをポンピングするだけです。

for(i=1;i<2e9;++i) {
    while (gtk_events_pending ()) {
        gtk_main_iteration ();
    }
}

これは、すべてのスレッドの問題を回避し、データ アクセスをロックする必要がないことを意味します。on_button1_clickedハンドラーで設定される反復ごとにブール値をチェックすることで、計算を停止できます。

于 2013-03-25T23:23:14.663 に答える
1

次のコードは、私が尋ねたことを実行します。pthread を使用します。それが最もエレガントかどうかはわかりませんが、うまくいくようです。トリックは 2 つのフラグを使用することでした。1 つはアイドル状態用で、もう 1 つはキャンセル要求用です。これにより、実際のコードでは珍しい「pthread_cancel」関数を使用してスレッドをキャンセルする必要がなくなります。

#include <gtk/gtk.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>

#define UNUSED(x) (void)(x)

#define handle_error_en(en, msg) do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

typedef struct _Data {
    GtkWidget *window1,
              *button1;
} Data;

static Data *data;

static pthread_mutex_t calcmutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_t calcthread=0;

static gboolean idle=TRUE,cancel_request=FALSE;

void *calcfunc(void *arg) {
    int i,s;
    UNUSED(arg);
    g_print("\tstarting thread\n");

    s = pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    if (s != 0) {
        handle_error_en(s, "pthread_setcancelstate");
    }

    gdk_threads_enter();
    gtk_button_set_label(GTK_BUTTON(data->button1),"Stop");
    gdk_threads_leave();

    g_print("\tstarting work...\n");
    for (i=0; i<100000000 ;++i) {

        /* check for cancelation */
        pthread_mutex_lock(&calcmutex);
        if ( cancel_request ) {
            g_print("\t[cancel request noted].\n");
            pthread_mutex_unlock(&calcmutex);
            break;
        }
        pthread_mutex_unlock(&calcmutex);

        /* do "calculation" */
        i=i*1*-1*1*-1;
    }
    g_print("\tdone work.\n");

    gdk_threads_enter();
    gtk_button_set_label(GTK_BUTTON(data->button1),"Start");
    gdk_threads_leave();

    pthread_mutex_lock(&calcmutex);
    cancel_request=FALSE;
    idle=TRUE;
    pthread_mutex_unlock(&calcmutex);

    g_print("\tdone thread.\n");
    pthread_exit(NULL);
}

void on_button1_clicked(GtkWidget *widget, gpointer *ldata) {
    int s;
    UNUSED(widget);
    UNUSED(ldata);
    g_print("entered on_button1_clicked\n");


    pthread_mutex_lock(&calcmutex);
    if ( idle ) {
        g_print("idle, starting thread\n");
        s = pthread_create(&calcthread, NULL, calcfunc, NULL);
        if (s != 0) {
            handle_error_en(s, "pthread_create");
        }
        idle=FALSE;
    } else {
        g_print("not idle and not first time, making canceling request.\n");
        cancel_request=TRUE;
    }
    pthread_mutex_unlock(&calcmutex);

    g_print("finished on_button1_clicked\n");
}

/* just defines our setup */
int main (int argc, char *argv[]) {

    g_thread_init(NULL);
    gdk_threads_init();
    gdk_threads_enter();

    gtk_init(&argc, &argv);

    data=g_slice_new0(Data);

    printf("initial idle is %d\n",idle);

    /* add widgets and objects to our structure */
    data->window1=gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_default_size(GTK_WINDOW(data->window1),250,250);
    data->button1=gtk_button_new_with_label("Start");
    gtk_container_add(GTK_CONTAINER(data->window1),GTK_WIDGET(data->button1));

    gtk_signal_connect(GTK_OBJECT(data->window1), "delete-event",
                       gtk_main_quit, NULL);
    gtk_signal_connect(GTK_OBJECT(data->button1), "clicked",
                           G_CALLBACK(on_button1_clicked), NULL);

    gtk_widget_show_all(GTK_WIDGET(data->window1));

    gtk_main();

    /* free the memory and stuff */
    g_slice_free(Data, data);
    pthread_mutex_destroy(&calcmutex);

    gdk_threads_leave();

    return 0;
}

これは警告なしでコンパイルされます

gcc -Wall -Wextra -Wconversion -pedantic `pkg-config --cflags --libs gtk+-2.0` start_stop.c -o start_stop
于 2013-03-31T22:19:37.183 に答える
0

Javaでは、そのスレッドでinterrupt()を呼び出すと、スレッドはInterruptedExceptionを取得し、例外ハンドラーのキャッチでクリーンアップするか、終了する前に最終的にブロックできます。

C にはいくつかのオプションがあります。

  • そのスレッドに を使用してシグナルを送信し、以前に を呼び出したコード内のポイントにkill()シグナル ハンドラを配置します。クリーンアップの場合、ゼロ以外の値が返されたときに何かを行うだけです。つまり、後続の呼び出しから再開されます。longjmp()setjmp()setjmp()longjmp()
  • コールpthread_cancel()。ここで得られる唯一の実際のクリーンアップは、以前に登録したキャンセル ハンドラがpthread_cleanup_push()呼び出されることです。
  • 揮発性変数またはロックで保護された変数のいずれかがあり、定期的に (たとえば、ループの反復ごとに 1 回) チェックされ、計算をキャンセルする必要があるときに何らかの値に設定されます。フラグが設定され、ループから抜け出すと、好きなことを行うことができるため、クリーンアップは簡単です。

私はそれらすべてを嫌います: シグナルは、部分的な障害 (ファイルやソケットの短い読み取りと書き込みなど) を処理errno==EINTRし、コードのあらゆる場所で正しく処理する必要があることを意味しますが、シグナルハンドラーに存在するあらゆる種類の落とし穴 (小さなスタックサイズなど) を回避します。安全に使用できるシステム コールの制限) はpthread_cancel()、スレッド関数とキャンセル ハンドラの間で状態をドラッグする必要があることを意味します。フラグは、キャッシュされていないメモリを読み取ったり、毎回ロックを取得したりする必要があるため、パフォーマンスに影響を与える可能性があります。頻繁にチェックしないと、フラグが設定されたときにスレッドがすぐに応答しなくなります。

于 2013-04-03T13:40:07.960 に答える