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?
答案 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,则应为每个循环添加一个锁。