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

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

4

5 回答 5

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 个字节码。这意味着任何其他线程的任何其他字节码都容易在这些字节码之间执行。这就是它不安全的原因,也是你应该使用锁的原因。

于 2013-11-12T08:55:10.787 回答
1

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

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

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

于 2013-11-12T08:47:04.117 回答
1

我认为这是读者作家的问题

你的问题似乎在这里:

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 的更新丢失

于 2013-11-12T08:47:54.780 回答
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()              <==============================**
于 2013-11-12T08:53:24.463 回答
1

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

例如,如果 thread1 执行以下操作,则现在 shared_balance = 0:

balance = shared_balance
            balance += 100

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

balance = shared_balance
            balance -= 100

现在thread2余额=-100 shared_balance=0

然后转到thread1:

shared_balance = balance

现在 shared_balance =100

最后转到thread2:

shared_balance = balance

现在 shared_balance = -100

所以,当循环结束时,结果不为 0。

如果你想得到结果 0,你应该给每个循环加一个锁。

于 2013-11-12T08:56:00.633 回答