Forum: PC-Programmierung Multithreaded mit GTK 2.0


von Jürgen W. (lovos)


Lesenswert?

Wenn man mit z.B. MS-Windows-GUI programmiert, und man hat einen 
Workerthread oder eine Callback-Funktion, dann kann diese der Win-Proc 
eine Botschaft (z.B. WM_USER) posten, die dann z.B. ein paar Updates 
bewirkt (z.B. Text in einem Textfeld).
Auch bei Java/Swing gibt es einfache und klare Strukturen um 
Window-Updates vom Worker-Thread zum EDT (Event Dispatch Thread) zu 
delegieren.
(GUI-Modifikation sollen nur von einem einzigen Thread ausgeführt 
werden)

Nur bei GTK komme ich mit diesem (Standard-) Problem nicht weiter.
Der UI-Thread verschwindet in gtk_main() und aus ist es.

Wenn man nach "multithreaded GTK" googelt, kommt man gleich auf die
g_threads_init() - gdk_threads_init()  gdk_threads_enter() - 
gdk_threads_leave() Methode, z.B.
http://blogs.operationaldynamics.com/andrew/software/gnome-desktop/gtk-thread-awareness

Aber das macht doch den Code kompliziert und bremst den Workerthread 
aus?!
Was wäre eine vernünftige Methode, um z.B. von einem 
Raw-Midi-In-Callback einen Counter (statisches/nicht-editierbares 
Textfeld) auf dem Hauptwindow zu erhöhen?

gtk_label_set_label() geht vermutlich nicht, da das vom Callback-Thread 
(synchron) ausgeführt würde. Man bräuchte so ein 
"gtk_Post_label_set_label()", was den Textfeld-Update asynchron vom 
gtk_main()-Thread ausführen lässt.

von Jürgen W. (lovos)


Lesenswert?

Ich habe mir so eine Struktur überlegt, die ganz gut funktioniert.
Die worker-threads schreiben update-Befehle in ein Fifo.
Parallel zum gtk_main() läuft noch ein anderer UI-Thread 
"winproc_thread",
der das Messagefifo "fifo" liesst und thread-safe die entsprechenden
Updates macht.
1
pthread_mutex_t mutex;
2
pthread_cond_t count_cv;
3
"fifo"
4
5
void *worker_thread_1(void *args) {
6
...
7
  pthread_mutex_lock(&mutex);
8
  insert_item(fifo, cmd1);
9
  number_items = num_item(fifo);
10
  pthread_mutex_unlock(&mutex);
11
  if (number_items==1) { //übergang von leerem fifo zu einem Eintrag
12
    pthread_cond_signal(&count_cv); // comsumer signalisieren
13
  }
14
...
15
}
16
17
void *worker_thread_2(void *args) {
18
...
19
  pthread_mutex_lock(&mutex);
20
  insert_item(fifo, cmd2);
21
  number_items = num_item(fifo);
22
  pthread_mutex_unlock(&mutex);
23
  if (number_items==1) { //übergang von leerem fifo zu einem Eintrag
24
    pthread_cond_signal(&count_cv); // comsumer signalisieren
25
  }
26
...
27
}
28
29
30
void *winproc_thread(void *args) {
31
  for (;;) {
32
    pthread_mutex_lock(&mutex);
33
    if (num_item(fifo)==0) {// keine Befehle im FIFO, bitte warten
34
      pthread_cond_wait(&count_cv, &mutex);
35
    }
36
    remove_item(fifo, &cmd); // get update-command
37
    pthread_mutex_unlock(&mutex);
38
39
    gdk_threads_enter();
40
   
41
    gtk_update( ...), // je nach command cmd
42
43
    gdk_threads_leave();
44
  }
45
}
46
47
48
int main(int argc, char *argv[]) {
49
50
...
51
52
if (!g_thread_create(winproc_thread, &ctx1, FALSE, &error)) {
53
  g_printerr ("Failed to create YES thread: %s\n", error->message);
54
  return 1;
55
}
56
57
gdk_threads_enter();
58
gtk_main();
59
gdk_threads_leave();
60
61
...
62
}

von Andreas B. (andreasb)


Lesenswert?

guint       g_idle_add                      (GSourceFunc function,
                                             gpointer data);

Adds a function to be called whenever there are no higher priority 
events pending to the default main loop. The function is given the 
default idle priority, G_PRIORITY_DEFAULT_IDLE. If the function returns 
FALSE it is automatically removed from the list of event sources and 
will not be called again.


mfg Andreas

von Jürgen W. (lovos)


Lesenswert?

Danke, das scheint eine gute Idee zu sein.
So eine "idle" function kann das Message-Fifo abarbeiten, und da sie vom 
main-thread aufgerufen wird, ist sie von vornherein "thread-safe" ohne 
die entsprechenden Funktionen (gdk_threads_enter() ...).

von Andreas B. (andreasb)


Lesenswert?

Jürgen G. schrieb:
> Danke, das scheint eine gute Idee zu sein.
> So eine "idle" function kann das Message-Fifo abarbeiten, und da sie vom
> main-thread aufgerufen wird, ist sie von vornherein "thread-safe" ohne
> die entsprechenden Funktionen (gdk_threads_enter() ...).

Doch, du musst Synchronisieren. Ich habe mir schon so den XServer 
gekillt, hat der garnicht gern...
[Edit] Auch Windows hat das garnicht gern...

http://developer.gnome.org/gdk/stable/gdk-Threads.html

Idles, timeouts, and input functions from GLib, such as g_idle_add(), 
are executed outside of the main GTK+ lock. So, if you need to call GTK+ 
inside of such a callback, you must surround the callback with a 
gdk_threads_enter()/gdk_threads_leave() pair or use 
gdk_threads_add_idle_full() which does this for you. However, event 
dispatching from the mainloop is still executed within the main GTK+ 
lock, so callback functions connected to event signals like 
GtkWidget::button-press-event, do not need thread protection.


mfg Andreas

von Jürgen W. (lovos)


Lesenswert?

Ein weiteren Nachteil hat dieser Ansatz dennoch:

Ich kann die idle-function natürlich nicht mit
1
pthread_cond_wait(&count_cv, &mutex)
schlafenlegen und bei Ankunft einer Message aufwecken.
Die idle-function muss immer erst prüfen ob im Fifo was drin ist und 
sofort zurückkehren. Das kann in einer busy-loop ausarten, je nachdem 
wie oft sie vom main-thread aufgerufen wird.
Mal ausprobieren ...

von Andreas B. (andreasb)


Lesenswert?

Jürgen G. schrieb:
> Ein weiteren Nachteil hat dieser Ansatz dennoch:
>
> Ich kann die idle-function natürlich nicht mit
>
1
> pthread_cond_wait(&count_cv, &mutex)
2
>
> schlafenlegen und bei Ankunft einer Message aufwecken.

Warum die Funktion nicht erst beim einfügen einer Message starten? Dann 
werden alle Messages abgearbeitet, und danach wird die Funktion nicht 
mehr aufgerufen, bis du das nächste mal etwas in die Queue einfügst.

> Die idle-function muss immer erst prüfen ob im Fifo was drin ist und
> sofort zurückkehren. Das kann in einer busy-loop ausarten, je nachdem
> wie oft sie vom main-thread aufgerufen wird.
> Mal ausprobieren ...

Das Problem hättest du dann nicht...


mfg Andreas

von .NET (Gast)


Lesenswert?

Ohne böse klingen zu wollen:

Ich verstehe nicht, warum man heute noch diese rudimentären APIs und 
dergleichen Ansätze verfolgt, es sei denn, es gibt ein rein akademisches 
Interesse oder ein hinreichend persönliches Problem, um seine Defizite 
mit "Spezial Wissen" (was keiner braucht) auszugleichen.

Es gibt doch mittlerweile eine Vielzahl an Alternativen, je nach Gusto.

Wenn man das Rad neu beschreibt, wird es wahrscheinlich nicht ganz rund.

von Jürgen W. (lovos)


Lesenswert?

> Warum die Funktion nicht erst beim einfügen einer Message starten?

Weil dann der Worker-Thread diesen Aufruf machen müsste?
Vermutlich ist g_idle_add() anders als die gtk-UI Funktionen, aber im 
Worker-Thread wollte ich diese Funktionen vermeiden. Wenn ich z.B. ein 
Peak-Can-Interface mit 500kBaud auslese, dann darf er einfach nicht auf 
unbekannt blockiert werden.

von Jürgen W. (lovos)


Lesenswert?

>Autor: .NET (Gast)
>Datum: 23.07.2012 21:38
>
>Ohne böse klingen zu wollen:
>
>Ich verstehe nicht, warum man heute noch diese rudimentären APIs

Ich denke, dass GTK eine sehr hochstehende, bequeme und umfangreiche API 
bietet. Verglichen mit Java/Swing/Awt sehe ich von der Komplexität keine 
großen Unterschiede. In null komma nix hat man Menus, Controls (Buttons 
...) eingebunden.
Xlib wäre umständlicher zu programmieren.

von Andreas B. (andreasb)


Lesenswert?

Jürgen G. schrieb:
>> Warum die Funktion nicht erst beim einfügen einer Message starten?
>
> Weil dann der Worker-Thread diesen Aufruf machen müsste?
Ja

> Vermutlich ist g_idle_add() anders als die gtk-UI Funktionen, aber im
> Worker-Thread wollte ich diese Funktionen vermeiden. Wenn ich z.B. ein
> Peak-Can-Interface mit 500kBaud auslese, dann darf er einfach nicht auf
> unbekannt blockiert werden.

Ja gut, g_idle_add() ist synchronisiert, kann daher blockieren. Wenn du 
es selbst machst kannst du ggf. sogar eine Atomic Linked List bauen. 
Mussten wir mal in einem Praktikum...

Jürgen G. schrieb:
>>Autor: .NET (Gast)
>>Datum: 23.07.2012 21:38
>>
>>Ohne böse klingen zu wollen:
>>
>>Ich verstehe nicht, warum man heute noch diese rudimentären APIs
>
> Ich denke, dass GTK eine sehr hochstehende, bequeme und umfangreiche API
> bietet. Verglichen mit Java/Swing/Awt sehe ich von der Komplexität keine
> großen Unterschiede. In null komma nix hat man Menus, Controls (Buttons
> ...) eingebunden.
> Xlib wäre umständlicher zu programmieren.

GTK ist lediglich mit der Sprache C dem Konfort von Java unterlegen.

Von der GUI Lib her ist GTK sehr schön aufgebaut. Zudem braucht es keine 
kostenpflichtige Software und ist auch nicht Closed Source wie z.B. 
.NET.


mfg Andreas

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.