Re: GTK and threaded applications



-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 2/3/2006 2:14 PM, kornelix wrote:
 I last reported that GTK was making my multi-threaded application run
as multiple single threads, one after the other. It was suggested to
lock only the GTK calls instead of the whole thread. I tried this.

I wrapped the individual GTK calls as follows:
  gdk_threads_enter();
      do GTK calls to update the GUI or get user input
  gdk_flush();
  gdk_threads_leave();

That looks reasonable, but remember, you don't wrap *all* gtk calls in
the application.  See below.

I managed to lock up my system so badly that I had to power off and
reboot.

It's highly doubtful that GTK caused this.

I think the core problem is that some of my functions can be
called from within main() dialogs and also from within thread dialogs.
Hence calling "gtk_threads_enter()" was likely invalid when being called
from a dialog that was initiated from a menu created in main(). 
Correct?  Then I need to write different versions of my functions, with
and without "gtk_threads_enter()" etc., and make sure the right version
is being called. Correct? Or perhaps keep track of whether I am in
main() or a thread, and conditionally execute the above function calls
(there must be a library function that can tell me if I am in a thread
or not).

I rarely encounter this problem, but when I do, usually I use a stub
function, like:

void do_important_gui_stuff {
   /* ... */
}

void do_important_gui_stuff_locked() {
    gdk_threads_enter();
    do_important_gui_stuff();
    gdk_threads_leave();
}

It's not beautiful, but it works.  More likely, you can avoid the issue
entirely by rethinking your program flow a bit.

If I have understood the implications correctly, then writing
multi-threaded applications with GTK may be possible, but I am almost
ready to give it up because of the complexity and limitations. I will
check how hard it will be to convert my application to QT or X11 Motif.

You're looking at this in an overly-complicated way.  Here are a few
guidelines, all of which probably have appropriate documentation somewhere:

1.  Start off your app with this:

g_thread_init(NULL);
gdk_threads_init();
gtk_init(&argc, &argv);

2.  Only call gtk_main() *once*, usually from your main() function in
the main thread.  Surround this with
gdk_threads_enter()/gdk_threads_leave().  If you start up your other
threads in main(), then surround the whole bit.  So you might have
something like:

gdk_threads_enter();
start_other_threads();
win = gtk_window_new(...);
// ...
gtk_widget_show_all(win);
gtk_main();
gdk_threads_leave();

3.  GTK signal handlers are *always* run in the main thread, and are
already inside the GDK lock, so do *not* call _enter()/_leave().

4.  Glib "source" fuctions (e.g., functions run via g_idle_add() or
g_timeout_add(), etc.), are *always* run in the main thread, but are
*not* inside the GDK lock, so gtk/gdk calls in source functions *must*
be surrounded by _enter()/_leave() pairs.

5.  And of course, gtk/gdk calls that you explicitly make in other
threads need to be surrounded by _enter()/_leave() pairs as well.

FYI, here is something I pulled from the Microsoft web site on Win32
programming with threads.
[snip]

That's a bit irrelevant: the Win32 API doesn't need to run on different
architectures, different OSes, and with different graphics-drawing backends.

GTK gurus, please put this on your roadmap. Threads need to work without
the user having to manage this with extra complexity and the risk of
subtle bugs.

They do.  You just need to work within some constraints.  Building
multithreaded applications (especially in a language like C or C++) will
always have gotchas and pitfalls.

GTK functions need to assume they are in a threaded
environment and do their own locking.

This would increase complexity quite a bit.  How is a GTK function to
know if it's being called inside the lock or not?  Recursive mutexes
would help, but with a performance penalty.  My bet is it would hurt
maintainability of GTK itself quite a bit.

Making the user worry about this is wrong.

The user doesn't have to worry about it.  The application developer
does.  If you're not familiar with how to the toolkit in a multithreaded
environment, sure, there's a learning curve, but I don't believe the 5
points I mentioned above are an unreasonable burden.

Since obtaining a lock requires nanoseconds, this should not
be a performance concern.

Do you have benchmarks to back this up?

Note that the method I outlined above isn't the One True Way.  If you
want to be totally safe, don't let your worker threads touch the GUI at
all.  Communicate with the main (GUI) thread via GAsyncQueues and Glib
idle/timeout functions.  With this method, you don't need to call
gdk_threads_init() or use gdk_threads_enter/leave() at all.

        -brian

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (MingW32)

iD8DBQFD490W6XyW6VEeAnsRAi+iAKCn2jz5ZxLBL4okH8UGsFJJIQTnugCeI4+7
TqZ53R1EEkj7/XkyqtL3NY8=
=1Tzg
-----END PGP SIGNATURE-----



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]