共计 2746 个字符,预计需要花费 7 分钟才能阅读完成。
并发和并行 | Python 中实现多线程 threading 和多过程 multiprocessing
昨天晚上组会轮到我汇报技术内容,最近正在和 ray 以及 spark 打交道,索性讲一下并发和并行。反正大家都是治理学院的,平时很少接触这种,因而这个选题不大可能因为内容根底而贻笑大方。
本文摆一摆并发和并行。附上很简略的 Python 代码,波及到自带库 threading 和 multiprocessing 的应用。
并发和并行
咱们简略用多线程对应并发,多过程对应并行。多线程并发更强调充分利用性能;多过程并行更强调晋升性能下限。
我用非常简单且不那么谨严的比喻来阐明。
多线程
一个 CPU 相当于一个学生。
一个学生一周开一次组会,换句话说一周给老师汇报一次工作。
老师个别会给学生同时安排几个工作,比方做较量、做我的项目、读论文,学生可能周一做做较量、周二读读论文、周三做做我的项目 … 到了组会,他就把三件事都拿进去汇报,老师很快慰,因为在老师的视角里:学生这三件事是同时在做的。
多线程也是同一个情理,假如你的手机只有一块单核 CPU。你的 CPU 这 0.01 秒用来播放音乐,下 0.01 秒用来解析网页 … 在你的视角里:播放音乐和解析网页是同时进行的。你大能够畅快地边听音乐边网上冲浪
何谓充分利用性能? 如果这学生只有一项工作,那他这一周可能只须要破费两天来做工作,剩下工夫摸鱼(针不搓,三点钟饮茶先!)。因而,咱们用「多线程」来让学生实现『并发』,充分利用学生能力。
在理论状况中,多线程、高并发这些词语更多地呈现在服务端程序里。比方一个网络连接由一个线程负责,一块 CPU 能够负责解决多个异步的申请,大大晋升了 CPU 利用率。
多过程
多个 CPU(CPU 的多核)相当于多个学生。
一个工作能够拆成几个工作相互协作、同时进行,则是多过程。
比方研究生课程,老师非得留个论文作业,都研究生了我去,留啥大作业。
那咱就多线程并行搞呗。确定了大略思路,剩下的一股脑写就行。咱队伍里一共甲乙丙丁四名同学,那就:
- 甲同学负责 Introduction
- 乙同学负责 Background
- 丙同学负责 Related Works
- 丁同学负责 Methodology
这是乙同学提出异议:不应该是先实现 Introduction 再写 Background,一个个来嘛?
大哥,都研究生了嗷,作业糊弄糊弄差不多得了啊。让你写你就写。
能够预知,上述四局部同时进行,怎么也比一个人写四块要快。
所以说 多过程并行晋升性能下限。
在理论状况中,多过程更多地与高性能计算、分布式计算分割在一起。
Python 实现
首先申明咱的试验环境。
> python --version
Python 3.8.5
咱们设置个工作:求数的欧拉函数值。
def euler_func(n: int) -> int:
res = n
i = 2
while i <= n // i:
if n % i == 0:
res = res // i * (i - 1)
while (n % i == 0): n = n // i
i += 1
if n > 1:
res = res // n * (n - 1)
return res
求一个数的欧拉函数值可能很快,然而一堆数呢?
所以咱想着用并行实现这个工作。
咱们把工作分成三份。
task1 = list(range(2, 50000, 3)) # 2, 5, ...
task2 = list(range(3, 50000, 3)) # 3, 6, ...
task3 = list(range(4, 50000, 3)) # 4, 7, ...
def job(task: List):
for t in task:
euler_func(t)
来看看平平无奇的失常串行。
@timer
def normal():
job(task1)
job(task2)
job(task3)
实现了 task1
再实现 task2
… 行,没故障。
看看多线程?
import threading as th
@timer
def mutlthread():
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()
再看看多过程?
import multiprocessing as mp
@timer
def multcore():
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()
# 上面是后果
同步串行:timer: using 0.24116 s
多线程并发:timer: using 0.24688 s
多过程并行:timer: using 0.13791 s
后果不太对,按理说,多过程并行
的耗时应该是 同步串行
的三分之一,毕竟三个等同体量的工作在同时进行。
多线程并发
比同步串行
慢是应该的,因为 多线程并发
和同步串行
的算力是一样的,然而多线程并发得在各个工作间来回切换,导致更慢。
你问 @timer
是什么意思?哦,这个是我写的润饰器,如下。
def timer(func):
@wraps(func)
def inner_func():
t = time.time()
rts = func()
print(f"timer: using {time.time() - t :.5f} s")
return rts
return inner_func
不太明确『Python 润饰器』的老铁,不如给我点个「在看」,再关注下我,咱们当前具体道来。
我是小拍,微信 PiperLHJ,感激关注与在看。