1
import threading

shared_balance = 0

class Deposit(threading.Thread):
    def run(self):
        for i in xrange(1000000):
            global shared_balance
            balance = shared_balance
            balance += 100
            shared_balance = balance

class Withdraw(threading.Thread):
    def run(self):
        for i in xrange(1000000):
            global shared_balance
            balance = shared_balance
            balance -= 100
            shared_balance = balance

thread1 = Deposit()
thread2 = Withdraw()

thread1.start()
thread2.start()

thread1.join()
thread2.join()


print shared_balance

Everytime I run this program it outputs a random number. If it deposits 100, 1 million times and withdraws 100, 1 million times then why isn't the output 0?

2
  • Closely related, possible dupe: Strange python threading output Commented Nov 12, 2013 at 8:45
  • 1
    You are assuming the two threads alternate; instead, they may both read and write at almost the same time. Both threads read one number, then both alter the number and write. One of the two writes will be lost as the other overwrites the balance. Commented Nov 12, 2013 at 8:46

5 Answers 5

4

You need to use threading.Lock to access you variables safely :

from threading import Thread, Lock

shared_balance = 0

class Deposit(Thread):
    def __init__(self, lock):
        super(Deposit, self).__init__()
        self.lock = lock
    def run(self):
        global shared_balance
        for i in xrange(1000000):
            with self.lock:
                shared_balance += 100

class Withdraw(Thread):
    def __init__(self, lock):
        super(Withdraw, self).__init__()
        self.lock = lock
    def run(self):
        global shared_balance
        for i in xrange(1000000):
            with self.lock:
                shared_balance -= 100

shared_lock = Lock()
thread1 = Deposit(shared_lock)
thread2 = Withdraw(shared_lock)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print shared_balance

Output :

>>> 0

Also, take a look at the bytecode generated for :

a = 0
def f():
    global a
    a += 10

Bytecode of "a += 10" :

 6 LOAD_GLOBAL              0 (a)     # Load global "a"  UNSAFE ZONE
 9 LOAD_CONST               2 (10)    # Load value "10"  UNSAFE ZONE
12 INPLACE_ADD                        # Perform "+="     UNSAFE ZONE
13 STORE_GLOBAL             0 (a)     # Store global "a" 
16 LOAD_CONST               0 (None)  # Load "None"
19 RETURN_VALUE                       # Return "None"  

In Python, a bytecode execution cannot be preempted. It makes it really convenient to use for threading. But in that case, it takes 4 bytecode executions to perform the '+=' operation. It means that any other bytecode of any other thread is suceptible to be executed between these bytecodes. This is what makes it unsafe and the reason why you should use locks.

Sign up to request clarification or add additional context in comments.

Comments

1

from here http://effbot.org/zone/thread-synchronization.htm

The reason for this is that the increment/decrement operation is actually executed in three steps; first, the interpreter fetches the current value of the counter, then it calculates the new value, and finally, it writes the new value back to the variable.

If another thread gets control after the current thread has fetched the variable, it may fetch the variable, increment it, and write it back, before the current thread does the same thing. And since they’re both seeing the same original value, only one item will be accounted for.

Comments

1

I think it's kind of the reader writer problem

Your problem seems to be here:

balance = shared_balance
balance += 100

Think about what happens, when you have the following execution order:

[...]
deposit thread:    balance = shared_balance
withdraw thread:   balance -= 100
deposit thread:    balance += 100
deposit thread:    shared_balance = balance
withdraw thread:   shared_balance = balance

The update on shared_balance is lost

Comments

1

Try lock.acquire() and lock.release() to fix your problem:

sorry for duplicating some code. I have marked the changes:

lock = threading.Lock()
class Deposit(threading.Thread):
  def run(self):
    for i in xrange(1000000):
      lock.acquire()               <==============================
      global shared_balance
      balance = shared_balance
      #print "[thread1] old: " + str(balance)
      balance += 100
      #print "[thread1] new: " + str(balance)
      shared_balance = balance
      #print "[thread1] shared: " + str(shared_balance)
      lock.release()               <==============================

class Withdraw(threading.Thread):
  def run(self):
    for i in xrange(1000000):
      lock.acquire()              <==============================
      global shared_balance
      balance = shared_balance
      #print "[thread2] old: " + str(balance)
      balance -= 100
      #print "[thread2] new: " + str(balance)
      shared_balance = balance
      #print "[thread2] shared: " + str(shared_balance)
      lock.release()              <==============================**

Comments

1

You use two thread to operator one shared_balance, the result is unforeseen.

For example, now shared_balance = 0 if thread1 do:

balance = shared_balance
            balance += 100

now thread1 balance =100 shared_balance=0 then it turns to thread2:

balance = shared_balance
            balance -= 100

now thread2 balance =-100 shared_balance=0

then it turns to thread1:

shared_balance = balance

now shared_balance =100

at last it turns to thread2:

shared_balance = balance

now shared_balance = -100

so , when loop end, the result is not 0.

If you want to get the result 0, you should add a lock to each loop.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.