并发与并行|Python中的多线程threading与多进程multiprocessing昨晚轮到我汇报小组会议的技术内容。我最近正在处理ray和spark。简单说一下并发和并行。反正大家都是管理学院出来的,很少接触这种话题,所以这个话题因为内容基础,不太可能被人笑话。本文讨论并发性和并行性。附上一段非常简单的Python代码,涉及到内置库threading和multiprocessing的使用。对于并发和并行,我们简单的用多线程对应并发,多进程对应并行。多线程并发更强调充分利用性能;多进程并行更强调性能上限的提升。我用一个很简单但不那么严格的类比来说明。多线程一个CPU相当于一个学生。一个学生每周开一次小组会,换句话说,每周向老师汇报一次。老师通常会同时给学生布置几项任务,比如做竞赛、做项目、看论文等。学生可能周一做竞赛,周二看论文,周三做项目……小组会上,他会做三件事老师很高兴的都报了出来,因为从老师的角度:学生正在同时做这三件事。多线程也是如此,假设你的手机只有一个单核CPU。你CPU的这0.01秒是用来播放音乐的,接下来的0.01秒是用来解析网页的……从你的角度来看:播放音乐和解析网页是同时进行的。您可以边上网边听音乐充分利用性能意味着什么?如果学生只有一份工作,那么他可能一周只需要花两天时间做任务,剩下的时间都在钓鱼(不是搓针,三点喝茶!)。因此,我们采用“多线程”,让学生实现“并发”,充分发挥学生的能力。现实中,多线程、高并发等词在服务器程序中出现的频率更高。比如一个线程负责一个网络连接,一个CPU可以处理多个异步请求,大大提高了CPU利用率。多进程多CPU(multi-coreofCPU)相当于多生。一个任务可以分解为多个相互配合、同时进行的任务,这就是多进程。比如研究生课程,老师要留一个论文作业,我毕业就去,所以我不留什么重大作业。然后让我们并行进行。确定了大意之后,剩下的就写了。我们组有A、B、C、D四位同学,即:A同学负责Introduction,B同学负责Background,C同学负责RelatedWorks,丁同学负责方法论。B同学提出异议:不应该先做再做Background,一个一个做。出色地?兄弟,我是研究生,作业乱七八糟就差不多了。如果你被要求写,你可以写。可以预见,以上四部分是同时进行的,比一个人写四篇要快。因此,多进程并行提高了性能的上限。实际上,多处理更多地与高性能计算和分布式计算相关联。Python实现首先声明我们的实验环境。>python--versionPython3.8.5我们来定一个任务:求数的欧拉函数值。defeuler_func(n:int)->int:res=ni=2whilei<=n//i:ifn%i==0:res=res//i*(i-1)while(n%i==0):n=n//ii+=1ifn>1:res=res//n*(n-1)returnres求一个数的欧拉函数值可能很快,但是一堆呢数字?于是我们想到并行完成这个任务。让我们把任务分成三个部分。task1=list(range(2,50000,3))#2,5,...task2=list(range(3,50000,3))#3,6,...task3=list(range(4,50000,3))#4,7,...defjob(task:List):fortintask:euler_func(t)我们来看看普通的serial。@timerdefnormal():job(task1)job(task2)job(task3)完成task1然后完成task2...好的,没有错。看看多线程?将线程导入为th@timerdefmutlthread():th1=th.Thread(target=job,args=(task1,))th2=th.Thread(target=job,args=(task2,))th3=th.Thread(target=job,args=(task3,))th1.start()th2.start()th3.start()th1.join()th2.join()th3.join()再看看多进程?将多处理导入为mp@timerdefmultcore():p1=mp.Process(target=job,args=(task1,))p2=mp.Process(target=job,args=(task2,))p3=mp.Process(target=job,args=(task3,))p1.start()p2.start()p3.start()p1.join()p2.join()p3.join()上面代码的逻辑如下:我创建一个线程/进程,其目的是完成任务job(task1)或job(task2)、job(task3),注意这里函数名和参数是分开的target=job,args=(task1,)然后start(),告诉线程/进程:你可以开始工作了,他们可以做自己的工作了。我们程序的主要逻辑还得继续往下运行到join()。这里我们的意思是让线程/进程阻塞我们的主要逻辑,比如p1。join()的意思是:p1没有完成它的工作,我的主要逻辑没有继续(它是“阻塞的”)。这样,我们的函数multcore结束后,里面所有的线程/进程任务都必须完成。我们看一下结果:if__name__=='__main__':print("同步串行:")normal()print("多线程并发:")mutlthread()print("多进程并行:")multcore()#下面是同步的结果Serial:timer:using0.24116sMulti-threadconcurrency:timer:using0.24688sMulti-processparallelism:timer:using0.13791s结果不正确。按理说多进程并行的耗时应该是同步串行的三分之一,毕竟三个同样大小的任务进行多线程并发应该比同步串行慢,因为计算能力多线程并发和同步串行是一样的,但是多线程并发要在任务之间来回切换,导致速度变慢。你问@timer是什么意思?哦,这是我写的装饰器,如下。deftimer(func):@wraps(func)definner_func():t=time.time()rts=func()print(f"timer:using{time.time()-t:.5f}s")returnrtsreturninner_func对于“Python修饰符”不太了解的小伙伴,不如帮我点一下“在看”,然后关注我,后面再详细说。我是小派,微信PiperLHJ,感谢大家的关注和观看。
