The signals and threads flying circus
Le samedi, janvier 31 2009, 00:10 :: gnome, hacking, python,
Aller au contenu | Aller au menu | Aller à la recherche
Disclaimer : if you don't program in python and don't know what a thread is, you probably want to skip this post as this is highly technically geekish loghorea which is not funny or even understandable in a remotely perverse way. Knowing some Flying Circus episodes might also help.
Say that you have two thread in your python application. One is "TV_speaker" and the other is "knight". Those two threads live in parallel their own live and all is good. But sometimes you want the "TV_speaker" to stop. In order to do that, you will ask the knight to throw a chicken at him. And the first thing that comes to mind is using a "signal".
You cant to call knight.send("chicken") and that, on the other side, tv_speaker.connect("chicken") handles things correctly.
Problem : using google, you find that, since 2.6, the python documentation is barely readable and unusable.
Hopefully, you find your way back to the good old and well organized 2.5 documentation only to discover that those signals are UNIX signals and have nothing in common with signals you can use in a GTK application.
Worse : those signals don't work with threads !
You seat down, cry and wait that a giant foot quickly stops your pain.

There's hope : GObject signals are just what you want. It's really simple : your signal sender (the knight) should be a subclass of gobject.GObject and that's nearly all. Well, you also have to define your signal with some obscure GObject terminology but copy/paste works well.
class knight(gobject.GObject):gsignals = { 'chicken': (gobject.SIGNAL_RUN_FIRST,\gobject.TYPE_NONE,\(str,)) }def init(self) :gobject.GObject.init(self)#Do your initialization herea = None
The definition of the signal is better explained here. The latest argument is the one of interest : it's the payload of your signal. It means hear that we will pass a string as an extra argument.
Now write the tv_speaker :
class tv_speaker :def init(self,emiter) :self.emiter = emiterself.emiter.connect("chicken",self.do)def do(self,param1,param2) :print "receiving the chicken on the head : my brain hurt"time.sleep(2)print "And now for something completely %s" %param2
As you can see, the receiver must have a reference to the emiter in order to catch the signal. We pass it in the constructor.
Now build the knight, the tv_speaker and send two chicken :
k = knight()tv = tv_speaker(k)k.emit("chicken","different")k.emit("chicken","random")
As you can see, the "do" function is called for each signal, one after the other. This works even if the knight is in another thread !
Download the code for this example
And what if you don't have a direct reference to the sender ? Your knight is not able to throw the chicken itself. Instead, Chapman will take the chicken from the knight and throw it at the tv_speaker.
All you have to do is to implement the "connect" method in chapman.
class chapman:def init(self,emiter) :self.emiter = emiterdef connect(self,signal,func) :self.emiter.connect(signal,func)
That's all ! If knight has a reference to chapman, chapman will transmit the signal for him :
k = knight()c = chapman(k)tv = tv_speaker(c)k.emit("chicken","different")k.emit("chicken","random")
Your output is sequential, illustrating that each signal is handled one after another :
receiving the chicken on the head : my brain hurtAnd now for something completely differentreceiving the chicken on the head : my brain hurtAnd now for something completely random
Download the code for this example
But what if you want to launch an action as soon as the signal is received without waiting for its result ? That's when you want to use threads.
Modify the do() function of the tv_speaker :
def do(self,param1,param2) :def reallydo(param) :print "receiving the chicken on the head : my brain hurt"time.sleep(2)print "And now for something completely %s" %paramt = threading.Thread(target=reallydo,args=[param2])t.start()
Now, the output is the following, illustrating that signals are handled in parallel :
receiving the chicken on the head : my brain hurtreceiving the chicken on the head : my brain hurtAnd now for something completely randomAnd now for something completely different
Warning : if your reallydo() function touch some GTK widget, you will experience crashes. That's because GTK is not thread safe. This problem is easy to solve : use GObject thread instead. Replace the two threading lines by :
gobject.idle_add(reallydo,param2)
You also have to tell GTK that you will use threads. So, just before gtk.main(), add :
gobject.threads_init()
That's it !
Download the code for this example
That's faire enough and, for that, we will use a lock. If the lock is already acquired by another thread, we will do nothing.
def do(self,param1,param2) :def reallydo(param) :print "receiving the chicken on the head : my brain hurt"time.sleep(2)print "And now for something completely %s" %paramself.lock.release()if self.lock.acquire(False) :t = threading.Thread(target=reallydo,args=[param2])t.start()else :print "We don't need you today"
Don't forget to release your lock, specially if you are catching error. You can release the lock in a "finally" statement.
Also, don't forget to make your lock module wide or application wide, depending on your need. It our example, the lock was instantiated in the tv_speaker.init() but if you have multiple tv_speaker, you might want to create the lock before and pass it as a constructor argument when instantiating the object.
We could also choose to wait for the lock to be released by simply calling "self.lock.acquire()" without a the "if" statement.
Download the code for this example
With the lock, we avoid unnecessary responses but, unfortunately we can lose information in a specific corner case : if the signal is received only a tiny fraction of second before we release the lock. It could mean that something has changed after we did our work but before we released the lock.
What we should do is the following : if any number of signals were received when the lock was acquired, we run the response one more time. It ensure that we don't miss anything and will not run too much responses (in the worst case, we will just run it once too much).
There's plenty of way of achieving that and I advise you to read of RLock, Semaphore and stuffs like that. I will give you my solution that I found simple, elegant and working well enough for my needs : protecting the lock by another lock that I call "lock_lock".
When receiving the signal, the tv_speaker will first try to acquire lock_lock. If he didn't succeed, nothing happens. If it succeed, it will call a new thread that will acquire the main_lock, waiting for it if needed, and then immediately releases the lock_lock. If another thread is already running, it means that main_lock will not be available and our thread will wait until its freed, keeping the lock_lock acquired all the time. All threads that will come afterwards will not do anything because they will not obtain lock_lock
def do(self,param1,param2) :def reallydo(param) :self.main_lock.acquire()self.lock_lock.release()print "receiving the chicken on the head : my brain hurt"time.sleep(2)print "And now for something completely %s" %paramself.main_lock.release()if self.lock_lock.acquire(False) :t = threading.Thread(target=reallydo,args=[param2])t.start()else :print "We don't need you today"
Download the code for this example

Never forget, when entering the threads world, that you lose predictability. Your software might works well hundreds of times and then, suddenly, erase all your data, rape your cat and kill your dogs. Avoid threads as much as possible and when you use, be sure to know what you are doing.
Happy coding !
Commentaires
Funny, to me the 2.5 signal docs are incomplete, don't have as much context and lack basic formatting as compared to 2.6. In fact, I usually (for quite a while now) read 2.6 docs even when using 2.5. So, YMMV :)
Daniel
Interesting post even if I'm not a python coder (at present time) and weird naming convention knight/tv speaker ;)
Anyway, this sounds like you hava a (in)direct reference to me:
k = knight()
c = chapman(k)
tv = tv_speaker(c)
tv -> c -> k
I wonder if there are other IPC signals if you really have no reference at all. Like message queue or something like that ?
Voilà exactement pourquoi j'utilise toujours de l'async à la place des threads. :)
Much needed, Thank you.
You should make it clearer that python (unix) signals have nothing in common with gobject signals other than a confusingly similar name
GObject signals are nothing but a list of functions, sequentially called when the signal is emitted, whereas unix signals are true asynchronous events delivered to the appropriate masked recipient.
In that regard GObject signals are considerably more predictable because the recipients are always called in the order to which they connect to said signal. It makes avoiding mutual locking errors easier. In certain cases locks can be avoided if you are aware of the sequence of connected signals and the knowledge that two GObject signal handlers are never truly running at exactly the same time.
It has always been the case on posix that (unix) signals within a multiple threaded application require delicate treatment. Google ("mixing threads and signals unix"), and considerable experience is your friend here.
Personally I would remove all reference to unix signals from the article to avoid confusion. The remainder of your article, the portions dealing with locking are a good reference though. Thanks
Hey, dude, you TOTALLY FORGOT to mention that a signal sent from one thread to another will execute the slot IN THE THREAD CONTEXT OF THE GOBJECT MAINLOOP. At best what you can do to make the receiver thread pick the signal up is set a member variable in the thread, and periodically check it in your thread loop.
Watch out, because this tutorial misses a critically important thing!
"Never forget, when entering the threads world, that you loose predictability."
That's of course only if you do it wrong.
Rudd-P : That's a very interesting point but, if you are in the mainloop, you will use idle_add anyway and it's clear that idle_add is in the main loop context. But try my examples : once the signal is catched, a new thread is started. I admit that this subject comprehension is still work in progress for me right now but I do believe that, in most of the case (and all the cases I've tested and encountered), what I described just works.
Philip > I should have said : "You loose simple predictability and you will start to use your brain." It's not a problem for people like you, it's a bit harder for people like me ;-)
@Rudd-P, ploum: Both of you are mistaken. The callback will *only* be run in the main context if it is emitted via idle_add, in other cases the connected signal handlers are run in the same context as the emitter, because if you remember, gobject "signals" are really just a list of function to be called immediately. Therefor when one calls emit, all functions connected are executed immediately from the current context.
See the following example for a demonstration
gist.github.com/56776
Sorry, to clarify
"The callback will *only* be run in the main context if it is emitted via idle_add, or from the main context itself"
You all probably know this, but Python threads are kind of "safe" anyway, as there is no true concurrency, instead Python interpreter runs like 100 statements, then switches to another "thread". Threads in Python only work as expected when waiting for certain operations, such as disk I/O. So basically, if you use threading module, base Python types such as dict and list are safe.
http://docs.python.org/glossary.htm...