Python多线程编程——资源共享
转载来源:https://github.com/jackfrued/Python-100-Days/blob/master/Day01-15%2F13.%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B.md
问题描述:
因为多个线程可以共享进程的内存空间,因此要实现多个线程间的通信相对简单,大家能想到的最直接的办法就是设置一个全局变量,多个线程共享这个全局变量即可。但是当多个线程共享同一个变量(我们通常称之为“资源”)的时候,很有可能产生不可控的结果从而导致程序失效甚至崩溃。如果一个资源被多个线程竞争使用,那么我们通常称之为“临界资源”,对“临界资源”的访问需要加上保护,否则资源会处于“混乱”的状态。下面的例子演示了100个线程向同一个银行账户转账(转入1元钱)的场景,在这个例子中,银行账户就是一个临界资源,在没有保护的情况下我们很有可能会得到错误的结果:
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
from time import sleep, time
from threading import Thread
class Account(object):
def __init__(self):
self._balance = 0
def deposit(self, money):
# 计算存款后的余额
new_balance = self._balance + money
# 模拟受理存款业务需要0.01秒的时间
sleep(0.01)
# 修改账户余额
self._balance = new_balance
@property
def balance(self):
return self._balance
class AddMoneyThread(Thread):
def __init__(self, account, money):
super().__init__()
self._account = account
self._money = money
def run(self):
self._account.deposit(self._money)
def main():
account = Account()
threads = []
start = time()
# 创建100个存款的线程向同一个账户中存钱
for _ in range(5):
t = AddMoneyThread(account, 1)
threads.append(t)
t.start()
# 等所有存款的线程都执行完毕
for t in threads:
t.join()
end = time()
print('账户余额为: ¥%d元,花费的时间为%.3f' % (account.balance, end - start))
if __name__ == '__main__':
main()
# 账户余额为: ¥1元,花费的时间为0.012
解决办法:
运行上面的程序,结果让人大跌眼镜,100个线程分别向账户中转入1元钱,结果居然远远小于100元。之所以出现这种情况是因为我们没有对银行账户这个“临界资源”加以保护,多个线程同时向账户中存钱时,会一起执行到new_balance = self._balance + money这行代码,多个线程得到的账户余额都是初始状态下的0,所以都是0上面做了+1的操作,因此得到了错误的结果。在这种情况下,“锁”就可以派上用场了。我们可以通过“锁”来保护“临界资源”,只有获得“锁”的线程才能访问“临界资源”,而其他没有得到“锁”的线程只能被阻塞起来,直到获得“锁”的线程释放了“锁”,其他线程才有机会获得“锁”,进而访问被保护的“临界资源”。下面的代码演示了如何使用“锁”来保护对银行账户的操作,从而获得正确的结果。示例:
# -*- coding: utf-8 -*-
from time import sleep, time
from threading import Thread, Lock
class Account(object):
def __init__(self):
self._balance = 0
self._lock = Lock()
def deposit(self, money):
# 先获取锁才能执行后续的代码
self._lock.acquire()
try:
# 计算存款后的余额
new_balance = self._balance + money
# 模拟受理存款业务需要0.01秒的时间
sleep(0.01)
# 修改账户余额
self._balance = new_balance
finally:
# 在finally中执行释放锁的操作保证正常异常锁都能释放
self._lock.release()
@property
def balance(self):
return self._balance
class AddMoneyThread(Thread):
def __init__(self, account, money):
super().__init__()
self._account = account
self._money = money
def run(self):
self._account.deposit(self._money)
def main():
account = Account()
threads = []
start = time()
# 创建100个存款的线程向同一个账户中存钱
for _ in range(5):
t = AddMoneyThread(account, 1)
threads.append(t)
t.start()
# 等所有存款的线程都执行完毕
for t in threads:
t.join()
end = time()
print('账户余额为: ¥%d元,花费的时间为%.3f' % (account.balance, end - start))
if __name__ == '__main__':
main()
# 账户余额为: ¥5元,花费的时间为0.055
讨论(非转载):
- 对于原作者的解决方法:
通过“锁”来保护“临界资源”
,对于一般的操作是个不错的方法,即:
在同一个线程里面对“临街资源”的访问和修改必须要在一个“锁”之内完成,这样可以保证线程对“临街资源”的修改对其他线程产生影响。 但是当对“临街资源”的访问和修改过程是一个比较耗时的操作的时候,即占了该线程所耗时间的绝大部分的时候,则会严重削弱多线程所带来的执行效率的提升
,如:-- coding: utf-8 --
from time import sleep, time
from threading import Thread, Lock
class Account(object):
def __init__(self):
self._balance = 0
self._lock = Lock()
def deposit(self, money):
# 先获取锁才能执行后续的代码
self._lock.acquire()
try:
# 计算存款后的余额
new_balance = self._balance + money
# 模拟受理存款业务需要2秒的时间
sleep(2)
# 修改账户余额
self._balance = new_balance
finally:
# 在finally中执行释放锁的操作保证正常异常锁都能释放
self._lock.release()
@property
def balance(self):
return self._balance
class AddMoneyThread(Thread):
def __init__(self, account, money):
super().__init__()
self._account = account
self._money = money
def run(self):
self._account.deposit(self._money)
def main():
account = Account()
threads = []
start = time()
# 创建100个存款的线程向同一个账户中存钱
for _ in range(5):
t = AddMoneyThread(account, 1)
threads.append(t)
t.start()
# 等所有存款的线程都执行完毕
for t in threads:
t.join()
end = time()
print('账户余额为: ¥%d元,花费的时间为%.3f' % (account.balance, end - start))
if __name__ == '__main__':
main()
# 账户余额为: ¥5元,花费的时间为10.004
在该例子中,线程“锁”内部的操作耗费了大量的时间(即:sleep(2)),多线程对执行效率的提升效果非常小。
# -*- coding: utf-8 -*-
from time import sleep, time
from threading import Thread, Lock
class Account(object):
def __init__(self):
self._balance = 0
self._lock = Lock()
def deposit(self, money):
sleep(2)
# 先获取锁才能执行后续的代码
self._lock.acquire()
try:
# 计算存款后的余额
new_balance = self._balance + money
# 模拟受理存款业务需要0.01秒的时间
sleep(0.01)
# 修改账户余额
self._balance = new_balance
finally:
# 在finally中执行释放锁的操作保证正常异常锁都能释放
self._lock.release()
@property
def balance(self):
return self._balance
class AddMoneyThread(Thread):
def __init__(self, account, money):
super().__init__()
self._account = account
self._money = money
def run(self):
self._account.deposit(self._money)
def main():
account = Account()
threads = []
start = time()
# 创建100个存款的线程向同一个账户中存钱
for _ in range(5):
t = AddMoneyThread(account, 1)
threads.append(t)
t.start()
# 等所有存款的线程都执行完毕
for t in threads:
t.join()
end = time()
print('账户余额为: ¥%d元,花费的时间为%.3f' % (account.balance, end - start))
if __name__ == '__main__':
main()
# 账户余额为: ¥5元,花费的时间为2.056
但是,当线程中“锁”外面的操作远耗时(sleep(2))大于“锁”内部的操作(sleep(0.01))的时候,多线程对执行效率的提升效果非常明显。
还没有评论,来说两句吧...