Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Basic threading in python

OK the code is pretty basic. Since I'm using multiple threads, and I want shared variables between them, I'm using a global.

Why does the code in ThreadClass sometimes not execute when I hit "C"? I know it's a concurrency problem, but I'm not sure how to fix it. I've reading up on semaphores and locking lately, but I'm not exactly sure how to implement it at the moment. Any suggestions are welcome.

import threading
buff_list = []

class ThreadClass(threading.Thread):
    global buff_list
    def run(self):
        while (True):
            if ("C" == raw_input()):
                buff_list.append("C")
                print buff_list

class ThreadClass2(threading.Thread):
    global buff_list
    def run(self):
        while(True):
            if ("B" == raw_input() and len(buff_list) > 0):
                buff_list.pop()
                print buff_list

a = ThreadClass()
b = ThreadClass2()

a.start()
b.start()
like image 388
user1413969 Avatar asked Jun 03 '13 21:06

user1413969


Video Answer


2 Answers

You have two synchronization problems here.

Let's deal with the easier one first, the fact that you're sharing a global buff_list that the two threads fight over. There's nothing stopping one thread from trying to append at the same time the other thread pops, which is illegal. And, even if you get lucky and that doesn't happen, the pop could come before the append.


The simplest way to solve this is to use a Queue, which is automatically-synchronizing:

buff_list = Queue.Queue()

Then just use put instead of append, and get instead of pop.


However, if you want to learn how to this stuff yourself, there are two possible ways to go.

First, you can use a Lock. (You can also use an RLock, but let's forget that for now.) This makes sure that only one thread is accessing buff_list at a time.

buff_lock = threading.Lock()
buff_list = []

Now, whenever you append or pop, just grab the lock:

with buff_lock:
    buff_list.append("C")

with buff_lock:
    val = buff_list.pop()

But this won't make sure the popping code waits until there's something to pop. If you want to do that, use a Condition:

buff_cond = threading.Condition()

Now:

with buff_cond:
    buff_list.append("C")
    buff_cond.notify()

with buff_cond:
    while not buff_list:
        buff_cond.wait()
    value = buff_list.pop()

The second problem is that you're implicitly sharing sys.stdin, because both threads are calling raw_input. Unless you have some way to synchronize things so that each thread knows when it's supposed to get the next input (and that may be hard to even describe, and you can't turn it into code if you can't describe it), that can't possibly work—every time you type C there's a 50/50 chance that the wrong thread will get it.

So, as kirelagin suggests, you need to make exactly one thread responsible for the I/O. The easiest way to do this is again to use a Queue, and have one thread put any inputs it doesn't use, and the other thread can get from the queue.

like image 88
abarnert Avatar answered Oct 20 '22 16:10

abarnert


Well you never know instance of which class got your input. If you hit “C” and that was ThreadClass2 reading your input, it will just do nothing since "B" == raw_input() will be False.

Exactly one thread should be responsible for I/O.

like image 43
kirelagin Avatar answered Oct 20 '22 17:10

kirelagin