了解互斥锁和连接,实现Python中安全有效的多线程。
微信搜索关注《Python学研大本营》,加入读者群,分享更多精彩
同步的重要性是什么?
假设有一个共享的家庭银行账户,余额为50美元,属于你和你父亲。
爸爸挣钱后把钱存进银行账户,不花钱,而你来花钱。
如果把这句话写成代码,从50美元的初始投资开始,爸爸向银行账户存入10美元,使总金额达到60美元。同时,你花了10美元,结果余额为50美元(60-10=50)。
然而,当爸爸和你同时进行交易时,会出现一个复杂的情况。
考虑到如果爸爸增加10美元,结果是50+10=60
,而如果你花10美元,结果是50-10=40
。就会少了10美元......
这就是所谓的“竞争条件”。
当有不良的程序设计,并且有几个进程和线程试图访问相同的资源时,就会出现“竞争条件”。
这种竞争条件是出了名的难以调试。可能有一个代码已经运行了数周而没有发生任何意外,然后在一天由于这种竞争条件而突然崩溃了。
进行编码
from threading import Thread
import timeclass JointAccount:current_balance = 50def dad(self):for i in range(1000000):self.current_balance += 10print('Dad saved!')def son(self):for i in range(1000000):self.current_balance -= 10print('Son Spent!')account = JointAccount()
Thread(target=account.dad, args=()).start()
Thread(target=account.son, args=()).start()
time.sleep(5) # 在这里,让两个线程都完成。
print(f"The current balance in the account is: {account.current_balance}")
输出是:
Dad saved!
Son Spent!
The current balance in the account is: -963810
可以看出,这里有一个错误!预测结果是60
,但观察到的是-963810
。如果再试一次,可以不断获得各种结果。
如何才能使这一问题得到解决?
如果要克服竞争条件,需要使用同步。
如何使用同步?
有2种同步技术。互斥锁是其中之一,另一种是利用连接。
1.互斥锁
为了理解这一点,可以想象一把锁保护一段特定的代码并且一次只允许一个线程访问它。
为了确保在任何给定时候只有一个线程可以访问银行账户,必须限制对场景中的current_balance
变量的访问。
假设没有其他人锁定current_balance
,爸爸将按照常规程序请求锁定它。爸爸在进行锁定调用后会收到一条信息,即他正持有该互斥锁。
在爸爸完成对current_balance
的更新后,他将解锁,以便其他线程可以访问它。儿子也将遵循类似的程序。
如果一个线程已经持有一个mutex
,而另一个线程请求它,请求的线程将被置入睡眠状态并一直等待,直到mutex
被释放。
如果两个线程同时尝试访问,则保证只有一个线程可以访问并锁定它。
锁的实现
from threading import Thread, Lock
import timeclass JointAccount:current_balance = 50mutex = Lock()def dad(self):for i in range(1000000):self.mutex.acquire()self.current_balance += 10self.mutex.release()print('Dad saved! ')def son(self):for i in range(1000000):self.mutex.acquire()self.current_balance -= 10self.mutex.release()print('Son spend!')account = JointAccount()
Thread(target=account.dad, args=()).start()
Thread(target=account.son, args=()).start()
time.sleep(5) # 在这里,让两个线程都完成。
print("current_balance remaining is: ", account.current_balance)
输出是:
Dad saved!
Son spend!
current_balance remaining is: 50
2.连接
什么是连接?
连接是一种使线程能够等待直到另一个线程执行完毕的方法。
实践连接
一个主线程创建一个子线程来执行任务。该子线程开始执行其任务。
在进入睡眠状态之前,主线程将调用join()
函数。join()
函数将保留主线程,直到子线程完成其任务并终止。
一旦子线程执行完毕,join()
函数将释放,主线程将被唤醒并继续执行。
单一任务的JOIN()
的实现
import time
from threading import Threaddef child():print("subThread is created!")print("subThread is doing work")time.sleep(10)print("subThread work's done")def parent():t = Thread(target=child, args=([]))t.start()print("mainThread thread put to sleep")t.join() # Parent将被封锁,直到child终止。# t.join(timeout=10)print("mainThread is awaken")
如果只是想让父母等待一段时间,就使用timeout
。
输出是:
subThread is created!
subThread is doing work
mainThread thread put to sleep
subThread work's done
mainThread is awaken
假设任务量很大,比如渲染图像。想尽可能迅速地完成这项任务。怎样才能做到这一点呢?为了加速渲染过程,将把图像分割成多个部分,并为每个部分分配不同的线程。将有一个主线程产生许多子线程。主线程等待所有的子线程完成渲染工作,它将与第一个子线程连接,开始渲染,接着是第二个子线程,以此类推,直到生成整个图像。为了实现这一点,将利用一个for循环。任何已经完成的线程将立即渲染。
首先,将尝试在不使用线程的情况下进行:
写一个脚本来查找一个词,然后将该词号与相应的短语相匹配。
matches = []passage1 = """This is a sample passage
with multiple lines and words.
It is meant to demonstrate how to
search for a specific word in Python."""# 循环浏览每一行和每一个词,找到特定的词
def word_search(word, passage):# 把这段话分成几行lines = passage.split("\n")for i, line in enumerate(lines):words = line.split()for j, w in enumerate(words):if w == word:matches.append(i+1)matches.append(j+1)if __name__ == '__main__':word_search('demonstrate', passage1)print("matched!")print("Sentence is :", matches[0])print("Word is :", matches[1])
现在以join()
的方式重写以上代码:
from threading import Lock, Threadmutex = Lock()
matches = []passage1 = """This is a sample passage
with multiple lines and words.
It is meant to demonstrate how to
search for a specific word in Python."""# 循环浏览每一行和每一个词,找到特定的词
def word_search(word, passage):# 把这段话分成几行lines = passage.split("\n")for i, line in enumerate(lines):words = line.split()for j, w in enumerate(words):if w == word:mutex.acquire()matches.append(i+1)matches.append(j+1)mutex.release()if __name__ == '__main__':t = Thread(target=word_search, args=(['demonstrate', passage1]))t.start()t.join() # 等待它完成print("matched!")print("Sentence is :", matches[0])print("Word is :", matches[1])
总结
理解互斥锁和连接对于在Python中实现安全和有效的多线程是至关重要的。互斥锁提供了一种方法,以确保一次只有一个线程访问共享资源,防止竞争条件和其他并发性问题。连接允许一个线程等待另一个线程完成后再继续,确保在程序终止前完成所有必要的工作。通过在多线程Python程序中加入这些技术,可以提高它们的安全性、效率和可靠性,从而提高整体性能,减少错误。
推荐书单
《Python从入门到精通(第2版)》
《Python从入门到精通(第2版)》从初学者角度出发,通过通俗易懂的语言、丰富多彩的实例,详细介绍了使用Python进行程序开发应该掌握的各方面技术。全书共分23章,包括初识Python、Python语言基础、运算符与表达式、流程控制语句、列表和元组、字典和集合、字符串、Python中使用正则表达式、函数、面向对象程序设计、模块、异常处理及程序调试、文件及目录操作、操作数据库、GUI界面编程、Pygame游戏编程、网络爬虫开发、使用进程和线程、网络编程、Web编程、Flask框架、e起去旅行网站、AI图像识别工具等内容。所有知识都结合具体实例进行介绍,涉及的程序代码都给出了详细的注释,读者可轻松领会Python程序开发的精髓,快速提升开发技能。除此之外,该书还附配了243集高清教学微视频及PPT电子教案。
《Python从入门到精通(第2版)》【摘要 书评 试读】- 京东图书京东JD.COM图书频道为您提供《Python从入门到精通(第2版)》在线选购,本书作者:明日科技,出版社:清华大学出版社。买图书,到京东。网购图书,享受最低优惠折扣!https://item.jd.com/13284890.html
精彩回顾
《深入浅析,一步步用GPT打造你的聊天机器人》
《用好这9个技巧,让你的Python代码“飞”起来》
《领略数学之美,使用Python创建分形图案》
《使用Python进行自动化录屏》
《轻松完成异步任务,一文搞懂Python Celery》
《ChatGPT插件使用攻略,解锁互联网新体验》
微信搜索关注《Python学研大本营》,加入读者群
访问【IT今日热榜】,发现每日技术热点