Python: two-way thread communication made simple

Posted in howto , python

cat-phone

You don’t really need a heavyweight communication system for very simple message passing between threads in python. Here’s a working example of how to implement your own thread class using pipes.

Of course, you can take the easy way out and use subprocess.Popen() and subprocess.communicate() or go with gobject’s spawn_async() which is very similar.

if __name__ == "__main__":

    def receiver(fd, condition, loop):
        msg = os.read(fd, 4096)
        print "msg received from thread: %s" % msg
        loop.quit()
        return False

    # two communication channels
    pipe1_read, pipe1_write = os.pipe()
    pipe2_read, pipe2_write = os.pipe()

    # enable non-blocking reads if you need to
    # this example doesn't need it
    fcntl.fcntl(pipe1_read, fcntl.F_SETFL, os.O_NONBLOCK)
    fcntl.fcntl(pipe2_read, fcntl.F_SETFL, os.O_NONBLOCK)

    loop = gobject.MainLoop()

    # watch for someone writing to pipe2
    gobject.io_add_watch(pipe2_read, gobject.IO_IN, receiver, loop)

    queue = Queue.Queue()
    item = 'thingy'
    # start a thread, pass in pipe1_read so he can listen
    # and pass in pipe2_write so it can talk back
    thread = MyThreadClass(pipe1_read, pipe2_write, queue)
    thread.setDaemon(True)
    thread.start()
    queue.put(item)

    time.sleep(.5)
    os.write(pipe1_write, 'hello thread!')

    loop.run()

Instances of your thread class need gobject.io_add_watch to handle receiving data from you. Now you can talk to your thread and he can receive your message and respond. If you need more than one thread, you should be able to let them use the same pipe for writing as long as you include something in the message that lets you know which thread just triggered your io_watch on pipe2_read. You should create a new channel for each one though, otherwise you have the problem of one thread taking data off the pipe and the rest can’t see it.

class MyThreadClass(threading.Thread):
    def __init__(self, pipe_r, pipe_w, q):
        self.pipe_read = pipe_r
        self.pipe_write = pipe_w
        self.queue = q
        threading.Thread.__init__(self)
        self._stop = threading.Event()

    def run(self):
        self.item = self.queue.get()

        watch = gobject.io_add_watch(self.pipe_read,
                        gobject.IO_IN, self.callback,
                        priority=gobject.PRIORITY_DEFAULT)

        counter = 0
        while not self.stopped():
            time.sleep(1)
            counter += 1
            print counter
            if counter > 2:
                os.write(self.pipe_write, "thread out of control!n")
                self.queue.task_done()

    def callback(self, fd, condition):
        msg = os.read(fd, 4096)
        print "msg recevied from main: %s" % msg
        os.write(self.pipe_write, 'hiya!')
        return True

    def stop(self):
        self._stop.set()

    def stopped(self):
        return self._stop.isSet()

The modules used here were fcntl, gobject, Queue, os, sys, threading, time. Not all completely necessary, but I wanted to create a working example that anyone could run. You could put the class and main together in a file using the if __name__ like I did, or put the class in another file and import the class from the file like this:

from mythreadclass import MyThreadClass

But I don’t see the point in separating them if you have only a single class and a main.

Posted by admica   @   13 September 2011

Related Posts

0 Comments

No comments yet. Be the first to leave a comment !
Leave a Comment

Name

Email

Website

*

Previous Post
«
Next Post
»
Powered by Wordpress   |   Lunated designed by ZenVerse

Valid XHTML 1.0 Transitional