Another try at interactive gui thread

I know the patch is intrusive, though maybe not as much

    > as it looks (everything except protect_mutex.py is
    > pretty trivial). I didn't expected it to be accepted
    > right away, I see it more as a contribution to the whole
    > gui thread discution.

    > As for gui_thread, I looked at it last year, I just
    > remember I didn't like that code that much. Maybe I
    > should get another look...

Most of what I've heard of gui thread (caveat, the situation may be
different now but this was the last I heard) was that the code was
hairy, required scipy distutils to build, was wx specific and had
problems that required significant work to fix. In that context, a
patch, even an intrusive one, that allowed gtk* and wx* to work from
an arbitrary python shell would be most welcome. While I think
ipython is the one true shell, there are many other shells in
existence and that won't change. If for example, this patch allowed
gtk* to be used from within IDLE or pycrust in addition to the
standard python shell, that would be a big win.

The problem we need to solve is substantially easier than the one
gui_thread wants to solve, if I understand the issues correctly. gui
thread enables you to interact with general wx apps from a general
python shell. We want gtk* and wx* backends to work from a general
python shell. If this patch addresses that, it's worth a close look.
Baptiste, have you have a chance to test with other shells? Would
your patch break gtk* or wx* under ipython? Does it work in IDLE
and/or pycrust? Do you have any ideas on how this would affect
performance? Obviously it adds some function call overhead -- with
collections the cases where there are large numbers of calls to the
backend renderers is reduced.

Another thought spoken as a threading newbie: could this be done more
simply by, eg subclassing RendererGTK and automating the protect
wrapping. This latter approach would satisfy a couple of points: it
would not be intrusive in the base class, could be made optional via
interactive setting or rc, and would not adversely impact performance
when it was not needed (on my quick read of the patch, it looks like
the last point is already handled by the call)

  if is_interactive():
      import_protect()

I'll give your patches a test-drive. Perhaps Steve could also comment
on the gtk changes.

JDH

John Hunter wrote:

"Baptiste" == Baptiste Carvello <baptiste13@...190...> writes:

    > I know the patch is intrusive, though maybe not as much
    > as it looks (everything except protect_mutex.py is
    > pretty trivial). I didn't expected it to be accepted
    > right away, I see it more as a contribution to the whole
    > gui thread discution.

    > As for gui_thread, I looked at it last year, I just
    > remember I didn't like that code that much. Maybe I
    > should get another look...

Most of what I've heard of gui thread (caveat, the situation may be
different now but this was the last I heard) was that the code was
hairy, required scipy distutils to build, was wx specific and had
problems that required significant work to fix. In that context, a
patch, even an intrusive one, that allowed gtk* and wx* to work from
an arbitrary python shell would be most welcome. While I think
ipython is the one true shell, there are many other shells in
existence and that won't change. If for example, this patch allowed
gtk* to be used from within IDLE or pycrust in addition to the
standard python shell, that would be a big win.

I certainly have never been a fan of digging too deep into gui_thread myself, I admit that much :slight_smile: Last time I looked at the code, I got a bit scared. On the other hand, Prabhu recently did a bunch of work on it, so it may be cleaner/better, I just don't know.

And as I said, I really was not speaking from the ipython side of things at all, I don't know why Baptiste took it that way (I didn't even mention ipython in my post). My opinion was strictly based on the generic approach offered by gui_thread, and the potential benefit of doing things that way.

The problem we need to solve is substantially easier than the one
gui_thread wants to solve, if I understand the issues correctly. gui
thread enables you to interact with general wx apps from a general
python shell. We want gtk* and wx* backends to work from a general
python shell. If this patch addresses that, it's worth a close look.

One thing that I like about gui_thread is that it works in a generic manner. What happens with this patch if someone uses matplotlib but adds new widgets? Or if they embed a matplotlib window into something else? Are such usage cases covered automatically, or do they require special handling by third-party code?

There certainly are benefits to a matplotlib-based self-contained solution, and gui_thread comes with its own baggage, so I'll let you guys judge where the balance rests.

Cheers,

f

Hi John and Fernando,

John, here is the quick answer to your questions:

* the patch does almost nothing (a few calls to a dummy function) except when the gui_protect rcParam is enabled

* when enabled the overhead is acceptable (on my Pentium III 500MHz), but import time of pylab is a little longer, because most of the work happens there. Also, only the functions and methods defined by matplotlib are protected. The idea is that if you call functions or methods from the gtk module, you ought to know what you're doing anyway (for that matter, I am not as ambitious as gui_thread).

* the patch currently doesn't work with wx because I've not finished the work, but it is doable. The GTK or WX mainloop is launched at the time the backend is imported, and only the one for the backend you are using will be launched.

* if you use interactive.py, or the code in IPython, you'd better not enable gui_protect. If you do, 2 GTK mainloops will be launched. This is allowed in GTK, but doesn't make sense.

* the GTK mainloop (probably same for WX) can run concurrently with tk. This means no problem plotting GTK from IDLE. You can even switch backends at runtime by reloading matplotlib.backends and matplotlib.pylab

* the GTK and WX mainloops cannot run concurrently. This is because, at least on Linux, WX itself launches a GTK mainloop (it's called WxGTK for a reason :). It might be possible to use this very mainloop to also display GTK figures, but I've had no luck with this until now.

Fernando, I've been looking again at gui_thread. I now understand the beauty and power of it, but it is also more brittle, which means a lot of work and testing. It might prove the best solution in the end, though.
I took no offense at all about IPython, so neither should you :slight_smile:
I just mentioned it because it is the advised solution in matplotlib doc.

Have a nice day,
BC

PS: You don't have to CC me, I'm subscribed to the list.

Regarding Baptiste's patch
- I'm not too keen on the idea of wrapping gtk.gdk.threads_enter() /
gtk.gdk.threads_leave() around every GDK function call.
- I share Fernando's concern with possible problems when adding new
widgets or embedding a matplotlib window into something else.

My understanding is that wrapping gtk.gdk.threads_enter() /
gtk.gdk.threads_leave() around GDK functions is not necessary if all the
GDK calls occur in the same thread. So I think a simpler solution is to
arrange for all gui calls to go to a single thread and do away with
enter()/leave().

I created a module that allows interactive matplotlib access from a
python shell, its a modified version of
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65109

This method seems to work for me (on Linux) and is not very intrusive to
matplotlib code - it just requires that 'gui_cmd()' is used to execute
all gui commands when in interactive mode.

Any comments?

Regards
Steve

mt.py (3 KB)

Steve Chaplin wrote:

I created a module that allows interactive matplotlib access from a
python shell, its a modified version of
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65109

This method seems to work for me (on Linux) and is not very intrusive to
matplotlib code - it just requires that 'gui_cmd()' is used to execute
all gui commands when in interactive mode.

Any comments?

In case anyone is interested, here's a lightweight, standalone (non-ipython) GTK threaded interactive interpreter with matplotlib support. This code is also based on the same ASPN recipe, it's basically the prototype I used to understand that recipe when I was adding the matplotlib support to IPython.

The threading tricks are the gold in that recipe, but the rest of the code is rather messy. I pretty much rewrote it when studying it (while keeping the threading core). Perhaps this may be of use to others. The ASPN recipe duplicates a lot of the functionality in the code.py module from the standard library, which is completely unnecessary (it's guaranteed to be there in any python installation). By using code.py, the whole thing can be rewritten much more cleanly (and you benefit from a good implementation in the stdlib for all the interpreter emulation parts).

I think I sent this to John a while back so he could replace the interactive.py shipped with matplotlib. I fully acknowledge in the header the ASPN recipe as the origin of the threading solution.

The pylab support in current ipython is basically this same idea, but generalized to work with either GTK or WX, and blended into the IPython code structure (that wasn't too bad, since ipython itself is based on code.py).

Cheers,

f

pyint-gtk.py (7.69 KB)

I may not have clearly explained my idea. I'm not proposing another
Python shell/interpreter to run matplotlib. The mt.py module is to test
an idea without needing to modify any matplotlib code.

To use it as intended would involve moving it into matplotlib and
getting matplotlib to call gui_cmd() to execute interactive gtk/gdk
calls in their own thread.
This is the part I'm not sure about - what is the minimal set of
functions to wrap with gui_cmd() to ensure that it encloses all gtk/gdk
functions?
gui_cmd() could be called from draw_if_interactive() and maybe
new_figure_manager(), I'm not sure where else it might be needed.
Also it would need to be fed import commands.

Steve

···

On Wed, 2005-01-26 at 12:17 +0800, Steve Chaplin wrote:

I created a module that allows interactive matplotlib access from a
python shell, its a modified version of
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65109

This method seems to work for me (on Linux) and is not very intrusive to
matplotlib code - it just requires that 'gui_cmd()' is used to execute
all gui commands when in interactive mode.

Steve Chaplin a écrit :

Regarding Baptiste's patch
- I'm not too keen on the idea of wrapping gtk.gdk.threads_enter() /
gtk.gdk.threads_leave() around every GDK function call.
- I share Fernando's concern with possible problems when adding new
widgets or embedding a matplotlib window into something else.

My understanding is that wrapping gtk.gdk.threads_enter() /
gtk.gdk.threads_leave() around GDK functions is not necessary if all the
GDK calls occur in the same thread. So I think a simpler solution is to
arrange for all gui calls to go to a single thread and do away with
enter()/leave().

This is mostly a question of taste.
A bug in the handling of threads_enter()/threads_leave() leads to a deadlock, while a bug with a gui thread yields "Xlib async reply". Choose which ugly death you prefer !

I created a module that allows interactive matplotlib access from a
python shell, its a modified version of
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65109

In fact your module fits well into the framework of my proposal: all you have to do is rename your gui_cmd to gtk_protect, and change a few things like passing the function directly, passing the arguments, and returning the value. Then you will have a drop-in replacement for protect_mutex.py.

This method seems to work for me (on Linux) and is not very intrusive to
matplotlib code - it just requires that 'gui_cmd()' is used to execute
all gui commands when in interactive mode.

This is where the real beef is.
There are several strategies:
* wrap everything: this is the examples/interactive.py strategy, simple, but the gui freezes on long calculations
* wrap all gui calls: this is the gui_thread strategy: elegant, but difficult
* wrap only pylab functions (draw_if_interactive etc), as you seem to suggest in your second email: imho this is not acceptable because it breaks method calls of matplotlib objects
* draw the line somewhere in the backend: this is what I proposed.

In the end, the backends developpers have the final say, because they will have to do the day-to-day maintenance.

Any comments?

Regards
Steve

Cheers, BC

PS: tell me if I need to CC you or not

In case anyone is interested, here's a lightweight, standalone (non-ipython)
GTK threaded interactive interpreter with matplotlib support. This code is
also based on the same ASPN recipe, it's basically the prototype I used to
understand that recipe when I was adding the matplotlib support to IPython.

Thanks, its a good example of code.InteractiveConsole, when I saw codeop in
the ASPN recipe I wondered why code.py was not used.

I noticed a few small points:
- GTKInterpreter says "Run a gtk mainloop() in a separate thread.", yet
gtk.main() runs in the main thread, and the interpreter/console runs in
a separate thread.
- gtk.timeout_add() is now deprecated in favour of gobject.timeout_add()

- runsource() and runcode() are synchronised using wait()/notify() so
they deal with one code object at a time. If self.code_to_run is changed
to a code queue (using list.append() and list.pop(0)) then the wait
()/notify() could be removed and the interpreter thread would return
immediately it adds the code to self.code_to_run and not when the main
thread has executed the code.

I think I sent this to John a while back so he could replace the
interactive.py shipped with matplotlib. I fully acknowledge in the header the
ASPN recipe as the origin of the threading solution.

John, this looks like an much improved interactive.py, is it OK to use
it to replace interactive.py in cvs, and if so should the '-pylab'
option be left as it is or removed to be compatible with the current
interactive.py?

Steve

···

On Tue, 2005-01-25 at 22:25 -0700, Fernando Perez wrote:

Steve Chaplin wrote:

In case anyone is interested, here's a lightweight, standalone (non-ipython) GTK threaded interactive interpreter with matplotlib support. This code is also based on the same ASPN recipe, it's basically the prototype I used to understand that recipe when I was adding the matplotlib support to IPython.

Thanks, its a good example of code.InteractiveConsole, when I saw codeop in the ASPN recipe I wondered why code.py was not used.

I noticed a few small points:
- GTKInterpreter says "Run a gtk mainloop() in a separate thread.", yet
gtk.main() runs in the main thread, and the interpreter/console runs in
a separate thread.
- gtk.timeout_add() is now deprecated in favour of gobject.timeout_add()

- runsource() and runcode() are synchronised using wait()/notify() so
they deal with one code object at a time. If self.code_to_run is changed
to a code queue (using list.append() and list.pop(0)) then the wait
()/notify() could be removed and the interpreter thread would return
immediately it adds the code to self.code_to_run and not when the main
thread has executed the code.

I am _really_ ignorant of thread issues, and so I have avoided touching the threading part of this code at all, though I've always suspected it could be cleaner. Would you mind applying these fixes and sending it back my way (including the GTK stuff)? I could then port your improvements over to the code in ipython proper which implements this same functionality.

Cheers,

f

···

On Tue, 2005-01-25 at 22:25 -0700, Fernando Perez wrote:

Steve Chaplin wrote:

In case anyone is interested, here's a lightweight, standalone (non-ipython) GTK threaded interactive interpreter with matplotlib support. This code is also based on the same ASPN recipe, it's basically the prototype I used to understand that recipe when I was adding the matplotlib support to IPython.

Thanks, its a good example of code.InteractiveConsole, when I saw codeop in the ASPN recipe I wondered why code.py was not used.

I noticed a few small points:
- GTKInterpreter says "Run a gtk mainloop() in a separate thread.", yet
gtk.main() runs in the main thread, and the interpreter/console runs in
a separate thread.

You are correct.

- gtk.timeout_add() is now deprecated in favour of gobject.timeout_add()

Feel free to fix it, I didn't know better.

- runsource() and runcode() are synchronised using wait()/notify() so
they deal with one code object at a time. If self.code_to_run is changed
to a code queue (using list.append() and list.pop(0)) then the wait
()/notify() could be removed and the interpreter thread would return
immediately it adds the code to self.code_to_run and not when the main
thread has executed the code.

On second thought, I don't think this is a good idea in general. The problem I see is the serialization of output. Imagine you start a long running job and you get the prompt back right away. If your job prints to stdout along the way (as is very common with scientific codes), your input line will get clobbered by this output.

If you could run in a gui window where you could associate the stdout for each input 'cell' with its own output one, it would be different. This is exactly how Mathematica notebooks work, they stack a queue of cells for running sequentially, you get your typing prompt back immediately, but the output of each command is collected in an Out[] cell next to the corresponding In[] one. In a plain terminal, I don't see how we can make this work (in fact, in a terminal Mathematica also serializes output for this very reason).

I have been toying with the idea of adding a way to 'background' processes automatically to ipython, by creating a list of worker threads for long running jobs. But the problem of handling stdout has me stumped.

I'm open to comments, though, or perhaps I'm missing something obvious here.

Best,

f

···

On Tue, 2005-01-25 at 22:25 -0700, Fernando Perez wrote:

So removing wait()/notify() would not be such a good idea, it might be
useful if you are only running gui code, but not when the command prints
to stdout.

To run a background process as a thread you would need to redirect its
stdout somewhere (StringIO for example). The problem with using threads
for this is that they share the stdout with the main thread, so if you
redirect the background threads stdout you will redirect the python
interpreters stdout too!
You could run a background process as an actual process and not a thread
(and Python 2.4 has a new subprocess module which unifies the previous
solutions of os.system, os.spawn*, os.popen*, popen2.*, commands.*). It
lets you redirect stdout, but is used for running executables, not
calling python functions.

It has me stumped too, how can you call a python function and redirect
its stdout independent of the python interpreters stdout?

Steve

···

On Thu, 2005-01-27 at 17:14 -0700, Fernando Perez wrote:

> - runsource() and runcode() are synchronised using wait()/notify() so
> they deal with one code object at a time. If self.code_to_run is changed
> to a code queue (using list.append() and list.pop(0)) then the wait
> ()/notify() could be removed and the interpreter thread would return
> immediately it adds the code to self.code_to_run and not when the main
> thread has executed the code.

On second thought, I don't think this is a good idea in general. The problem
I see is the serialization of output. Imagine you start a long running job
and you get the prompt back right away. If your job prints to stdout along
the way (as is very common with scientific codes), your input line will get
clobbered by this output.

If you could run in a gui window where you could associate the stdout for each
input 'cell' with its own output one, it would be different. This is exactly
how Mathematica notebooks work, they stack a queue of cells for running
sequentially, you get your typing prompt back immediately, but the output of
each command is collected in an Out[] cell next to the corresponding In[] one.
  In a plain terminal, I don't see how we can make this work (in fact, in a
terminal Mathematica also serializes output for this very reason).

I have been toying with the idea of adding a way to 'background' processes
automatically to ipython, by creating a list of worker threads for long
running jobs. But the problem of handling stdout has me stumped.

I'm open to comments, though, or perhaps I'm missing something obvious here.

I've finally got round installing the interactive gui thread patch to
have a look at it. It worked quite well for a few simple plots I tried.

A bug in the handling of threads_enter()/threads_leave() leads to a
deadlock, while a bug with a gui thread yields "Xlib async reply".
Choose which ugly death you prefer !

What situation would yield an "Xlib async reply"?

There are several strategies:

...

* wrap only pylab functions (draw_if_interactive etc), as you seem to
suggest in your second email: imho this is not acceptable because it
breaks method calls of matplotlib objects

How does it break method calls of matplotlib objects? In interactive
mode the gtk backend does not work (by itself) so there's nothing to
break, and in non-interactive mode the gui_cmd() function would not be
used.

When thinking about using threads for interactive mode I had been
assuming people using the interactive mode would be using the pylab
interface, does anyone use the matplotlib class interface in interactive
mode?

I have a few questions about patch itself:
In import_gtk() there is time.sleep(1) - what is that for?

Shouldn't widget creation be protected as well by wrapping things like
new_figure_manager() and FigureCanvasGTK(). I know __init__ is wrapped
but objects may have __new__ methods which call gdk/gtk functions before
__init__ is called.

How about if a widgets standard methods such as show(), present() etc
are used - they are not protected (unless called from a protected
method). I think this just applies to using the class interface in
interactive mode, so it may be rarely encountered but might be a
problem.

Why use gtk_protect(), exec() and GTK_TEMPLATE, when you could use:

def gtk_protect (fun):
    def decorator (*args, **kwargs):
  if IN_GTK or threading.currentThread() is Thread:
            return fun(*args, **kwargs)
        IN_GTK.append (fun)
        gtk.threads_enter()
        try:
            res = fun(*args, **kwargs)
        finally:
            gtk.threads_leave()
            IN_GTK.pop()
            return res
    return decorator

Steve

···

On Wed, 2005-01-26 at 22:18 +0100, Baptiste Carvello wrote:

Steve Chaplin wrote:

To run a background process as a thread you would need to redirect its
stdout somewhere (StringIO for example). The problem with using threads
for this is that they share the stdout with the main thread, so if you
redirect the background threads stdout you will redirect the python
interpreters stdout too!
You could run a background process as an actual process and not a thread
(and Python 2.4 has a new subprocess module which unifies the previous
solutions of os.system, os.spawn*, os.popen*, popen2.*, commands.*). It
lets you redirect stdout, but is used for running executables, not
calling python functions.

It has me stumped too, how can you call a python function and redirect
its stdout independent of the python interpreters stdout?

Not as far as I know. That's why I think the only solution for the whole 'backgrounding' problem with stdout is to put everything into a gui window (a la mathematica notebooks). I've been thinking about it, and the proper way to write it is probably with _two_ python processes. One would be the gui environment, and the other would be just a 'kernel' executing commands. The stdout of the kernel would then be connected to an object capable of putting the results of each command next to the input cell which generated them. I bet that's pretty much how Mathematica is organized, simply because it looks like the only reasonable way to deal with the issue.

Cheers,

f

Hi,

Steve Chaplin a écrit :

What situation would yield an "Xlib async reply"?

using your mt module:
   >>> import mt
   >>> mt.gui_cmd('print "OK"')
   OK
   >>> import matplotlib
   >>> matplotlib.interactive(1)
   >>> import pylab
   Xlib: unexpected async reply (sequence 0x4b)!

This is on linux, I don't know what you get on other platforms, but I guess it's just as bad.

How does it break method calls of matplotlib objects? In interactive
mode the gtk backend does not work (by itself) so there's nothing to
break

Well, it depends on how ambitious you are :slight_smile:

When thinking about using threads for interactive mode I had been
assuming people using the interactive mode would be using the pylab
interface, does anyone use the matplotlib class interface in interactive
mode?

I sometimes do. However, I've painted the situation worse than it is. I almost only use the setter/getter methods, which are not dangerous.

One annoying thing though: error_msg will hang the gui:
   >>> import mt
   >>> mt.gui_cmd('from pylab import *')
   >>> mt.gui_cmd('plot([1,2])')
   >>> mt.gui_cmd('plot([1,2],"0r")')
   *** gui hangs here, you get the prompt back, but no gui commands will

       work ***

In import_gtk() there is time.sleep(1) - what is that for?

This is an attempt to avoid a race condition were a proxy function would be called before the GTK thread is initialized. I don't know if this would lead to a real problem. 1s wait not that bad anyway.

Shouldn't widget creation be protected as well by wrapping things like
new_figure_manager() and FigureCanvasGTK(). I know __init__ is wrapped
but objects may have __new__ methods which call gdk/gtk functions before
__init__ is called.

I didn't think of that. Most GTK object are not dangerous until they are shown.

How about if a widgets standard methods such as show(), present() etc
are used - they are not protected (unless called from a protected
method). I think this just applies to using the class interface in
interactive mode, so it may be rarely encountered but might be a
problem.

As I stated, I didn't try to protect methods inherited from GTK objects (ie not defined in matplotlib). Which means people using them are on their own. You have to draw the boundary somewhere, unless you want to implement a full gui_thread.

Why use gtk_protect(), exec() and GTK_TEMPLATE, when you could use:

def gtk_protect (fun):
    def decorator (*args, **kwargs):

Well this was a try to set the proxy function name as the real function name, in order to avoid weirdeness when reading object help. But I could not set the argument spec correctly, so I ended up adding the 'proxy from' line to the docstring. So I think all the GTK_TEMPLATE machinery serves no purpose now.

As a final note, I have to say that the latest version of interactive.py is good enough for my needs, so I'm no longer very motivated for pushing this patch.

If you just need the pylab interface, you can also do it without patching matplotlib. Just create a protected_pylab.py module, where you would start the gui thread, define the gtk_protect function, import pylab and define command=gtk_protect(pylab.command) for every command in pylab. Then doing 'from protected_pylab import *' would give you a working pylab interface, with just the error_msg problem.

Cheers,
Baptiste

Baptiste Carvello wrote:

Hi,

Steve Chaplin a écrit :

When thinking about using threads for interactive mode I had been
assuming people using the interactive mode would be using the pylab
interface, does anyone use the matplotlib class interface in interactive
mode?

I sometimes do.

Agreed.

One should also be able to execute interactively large scripts which may have been written to the OO interface. I know that I routinely test 10k LOC modules from within ipython (though not matplotlib based). It would be IMHO poor design to put all sorts of special-case warnings, where certain features of matplotlib would only be available in certain ways of using it. Special cases lead to madness...

Note that I haven't followed the details close enough to vouch for any particular approach. I just would like to see any solution implemented to be as clean and general as possible. As far as users are concerned, these details should be, where possible, totally irrelevant (internal implementation only). As a reference, ipython tries mightily hard to make sure that _any_ code which can be executed at a system command line as 'python foo.py' produces identical behavior at an ipython prompt via 'run foo'. There are special options to 'run' for modifying this, but the default attempts to emulate python very faitfully, just with better tracebacks, debug support, etc. And I consider deviations from this bevavior as bugs. It's not 100% perfect, but it really works fairly well in most cases.

Regards,

f

The gdk.threads_init() entry in the pygtk manual says:
http://www.pygtk.org/pygtk2reference/gdk-functions.html#function-gdk--
threads-init
"PyGTK does not release the GIL when calling a GTK or GDK function. Also
it does not acquire the GDK global lock (GGL). This means that, in
effect, Python threads can use the GIL alone to serialize access to the
GTK and GDK libraries. Of course, if there are non-Python threads
calling GTK or GDK functions the GGL must be used."

I read this to mean that Python programs with no non-Python threads,
like matplotlib, can run gtk.main in its own thread and do not need to
use gtk_protect (for gdk.threads_enter/leave GGL locking) since the
Python GIL will serialise access to GTK/GDK libraries.

That would be an easy solution to the interactive GTK problem - just
start gtk.main in its own thread with no need to alter any other code!
It sounds too simple to be true, have I missed something?

Steve