程序行为的解释(Python)

时间:2013-11-12 08:42:24

标签: python multithreading python-2.7

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

每次我运行这个程序时,它都会输出一个随机数。如果它存入100,100万次并提取100,100万次,那么为什么输出不是0?

5 个答案:

答案 0 :(得分:4)

您需要使用threading.Lock安全地访问变量:

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

输出:

>>> 0

另外,请查看为:

生成的字节码
a = 0
def f():
    global a
    a += 10

字节码" 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"  

在Python中,字节码执行不能被抢占。它使用线程非常方便。但在这种情况下,执行' + ='需要4个字节的代码执行。操作。这意味着任何其他线程的任何其他字节码都易于在这些字节码之间执行。这就是使它不安全的原因以及你应该使用锁的原因。

答案 1 :(得分:1)

从这里http://effbot.org/zone/thread-synchronization.htm

原因是增量/减量操作实际上分三步执行;首先,解释器获取计数器的当前值,然后计算新值,最后,它将新值写回变量。

如果另一个线程在当前线程获取变量后获得控制权,它可以在当前线程执行相同操作之前获取变量,递增变量并将其写回。由于它们都看到了相同的原始值,因此只会考虑一个项目。

答案 2 :(得分:1)

我认为这有点像reader writer problem

你的问题似乎在这里:

balance = shared_balance
balance += 100

当您拥有以下执行顺序时,请考虑会发生什么:

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

shared_balance的更新丢失

答案 3 :(得分:1)

尝试lock.acquire()lock.release()来解决您的问题:

抱歉复制一些代码。我已经标记了这些变化:

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()              <==============================**

答案 4 :(得分:1)

使用两个线程来操作一个shared_balance,结果是无法预料的。

例如, 现在shared_balance = 0 如果thread1执行:

balance = shared_balance
            balance += 100

现在thread1 balance = 100 shared_balance = 0 然后它转向thread2:

balance = shared_balance
            balance -= 100

现在thread2 balance = -100 shared_balance = 0

然后转向thread1:

shared_balance = balance

现在shared_balance = 100

最后转向thread2:

shared_balance = balance

现在shared_balance = -100

所以,当循环结束时,结果不是0.

如果要获得结果0,则应为每个循环添加一个锁。