The direct or gtk-lock method

The first method I'm exploring is what I call the direct method. Each thread will put its results on the canvas itself. This method is implemted in demo1a.py. In order to have this work smoothly gtk provides us with two functions gtk.gdk.threads.enter and gtk.gdk.threads_leave. These two functions manipulate a hidden lock. gtk.gdk.threads_enter will acquire the lock and gtk.gdk.threads_leave will release it. They should be used as a pair and put around any code that call a gtk.gdk function. Fleshing out the working threads a bit more we get the following.

    class Counting (Thread):
    
        def __init__(self, Id)
    
        def run(self):
            while ...:
                Calculate Results 
                gdk.threads_enter()
                canvas.Adjust(self.Id, self.ShowTime , self.ShowValue)
                gdk.threads_leave()

        def Start_Stop(self,ignore)
    
        def Modus(self,ignore):
            if Running:
                Change Mode
            else:
                Reset Values
                canvas.Adjust(self.Id, self.Time , self.Value)
		
        def Quit(self):
            Make thread leave loop

Now the question is: Why use the lock functions in the run method but not in the Modus method. The answer is that the Modus function is called as a signal handler by the gtk thread. When a signal handler is called by gtk it will embed the call to the handler between a gdk.threads_enter - gdk.threads_leave pair. So putting gdk.threads_enter() and gdk.threads_leave() in the Modus method ourselves would result in a deadlock, because we would try to acquire the same lock twice in the same thread.

In my opinion this makes this method useless as a general method. It is possible that it is workable in specific cases, but once you get more complicated code, where it isn't always obvious which threads will call which functions, this may soon turn into a debugging nightmare. And this is not the end of our problems. Look at the On_Delete method. It goes about like the following.

    def On_Delete(self, widget, evt, data=None):
        gdk.threads_leave()
        for W in sample(Worker,7):
            W.Quit()
        for W in sample(Worker,7):
            W.join()
        gdk.threads_enter()
        gtk.main_quit()
        return False

Now why should we have to put this handler between a reversed pair of lock function? The reason is that because it is a handler gdk.threads_enter is already called when this is invoked. So we now let all the threads quit and collect them with a join. However each thread could be just ready to call gdk.threads_enter and thus block. So trying to join the threads without releasing the gdk lock would probably result in a deadlock.

So if you really want to have gtk calls in the threads and your program is getting complicated, I suggest you do as follows. First include the following function.

    def Connect(obj, signal, handler, *args):

        def wrap(handler):
            def wrapper(obj, *args):
                gdk.threads_leave()
                handler(obj, *args)
                gdk.threads_enter()
            #end wrapper

            return wrapper
        #end wrap
    
        obj.connect(signal, wrap(handler), *args)
    #end Connect

Then replace all statements of the form:

    Object.connect(args, ...)

Into the following:

    Connect(Object, args, ...)

By doing this you have removed all implicite locks from the signal handlers. This means you can write your application without having to worry whether particular code will be called by a thread, a signal handler or both. Just enclose all gtk calls in the gdk.threads_enter() gdk.threads_leave() pair. This idea is worked out in in demo1b.py.

You should be informed that the gdk lock calls seem to do more than simple locking. So using a Lock from the threading module and acquiring and releasing it in the appropiate places will likely result in a frozen application. People who want to see for themselves can try demo1c.py

PREV NEXT