python多线程和多进程分别是什么?适合哪些场景?
Admin 2021-08-19 群英技术资讯 1696 次浏览
这篇文章主要介绍python多线程和多进程,很多新手对于进程和线程的区别不是很了解,下文实例对大家学习python线程和进程有一定的参考价值,感兴趣的朋友不妨继续往下看,希望大家阅读完这篇文章能有所收获,接下来小编带着大家一起了解看看。

并发通常应用于 I/O 操作频繁的场景,并行则更多应用于 CPU heavy 的场景。
并发(concurrency),指同一时刻只能有一条指令执行,多个线程的对应的指令被快速轮换地执行,线程/任务之间会互相切换。
并行(parallel) 指同一时刻,有多条指令在多个处理器上同时执行
注意:具体是并发还是并行取决于操作系统的调度。
多线程/多进程是解决并发问题的经典模型之一。
在一个程序进程中,有一些操作是比较耗时或者需要等待的,比如等待数据库的查询结果的返回,等待网页结果的响应。这个线程在等待的过程中,处理器是可以执行其他的操作的,从而从整体上提高执行效率。
比如网络爬虫,在向服务器发起请求之后,有一段时间必须要等待服务器的响应返回,这种任务属于 IO 密集型任务。对于这种任务,启用多线程可以在某个线程等待的过程中去处理其他的任务,从而提高整体的爬取效率。
还有一种任务叫作计算密集型任务,或者称为CPU 密集型任务。任务的运行一直需要处理器的参与。如果使用多线程,一个处理器从一个计算密集型任务切换到另一个计算密集型任务,处理器依然不会停下来,并不会节省总体的时间,如果线程数目过多,进程上下文切换会占用大量的资源,整体效率会变低。
所以,如果任务不全是计算密集型任务,我们可以使用多线程来提高程序整体的执行效率。尤其对于网络爬虫这种 IO 密集型任务来说,使用多线程会大大提高程序整体的爬取效率,多线程只适合IO 密集型任务。
由于 Python 中 GIL 的限制,导致不论是在单核还是多核条件下,在同一时刻只能运行一个线程,导致 Python 多线程无法发挥多核并行的优势。
GIL 全称为 Global Interpreter Lock(全局解释器锁),是 Python 解释器 CPython 中的一个技术术语,是Python之父为了数据安全而设计的。
CPython 使用引用计数来管理内存,所有 Python 脚本中创建的实例,都会有一个引用计数,来记录有多少个指针指向它。当引用计数只有 0 时,则会自动释放内存。每隔一段时间,Python 解释器就会强制当前线程去释放 GIL,Python 3 以后版本的间隔时间是 15 毫秒。
在 Python 多线程下,每个线程轮流执行:

某个线程想要执行,必须先拿到 GIL,并且在一个 Python 进程中,GIL 只有一个,导致即使在多核的条件下,同一时刻也只能执行一个线程。每一个线程执行完一段后,会释放 GIL,以允许别的线程开始利用资源。
执行一个CPU 密集型任务:
import time
import os
def cpu_bound_task(n):
print('当前进程: {}'.format(os.getpid()))
while n > 0:
n -= 1
if __name__ == "__main__":
print('主进程: {}'.format(os.getpid()))
start = time.time()
for i in range(2):
cpu_bound_task(100000000)
end = time.time()
print(f"耗时{end - start}秒")
输出:
主进程: 10104
当前进程: 10104
当前进程: 10104
耗时10.829032897949219秒
import os
import threading
import time
def cpu_bound_task(n,i):
print(f'子线程 {threading.current_thread().name}:{os.getpid()} - 任务{i}')
while n > 0:
n -= 1
if __name__=='__main__':
start = time.time()
print(f'主线程: {os.getpid()}')
thread_list = []
for i in range(1, 3):
t = threading.Thread(target=cpu_bound_task, args=(100000000,i))
thread_list.append(t)
for t in thread_list:
t.start()
for t in thread_list:
t.join()
end = time.time()
print(f"耗时{end - start}秒")
输出:
主线程: 1196
子线程 Thread-1:1196 - 任务1
子线程 Thread-2:1196 - 任务2
耗时10.808091640472412秒
可以发现多线程对CPU 密集型任务性能没有提升效果。
from multiprocessing import Process
import os
import time
def cpu_bound_task(n,i):
print(f'子进程: {os.getpid()} - 任务{i}')
while n > 0:
n -= 1
if __name__=='__main__':
print(f'父进程: {os.getpid()}')
start = time.time()
p1 = Process(target=cpu_bound_task, args=(100000000,1))
p2 = Process(target=cpu_bound_task, args=(100000000,2))
p1.start()
p2.start()
p1.join()
p2.join()
end = time.time()
print(f"耗时{end - start}秒")
输出:
父进程: 22636
子进程: 18072 - 任务1
子进程: 9580 - 任务2
耗时6.264241933822632秒
也可以使用Pool类创建多进程
from multiprocessing import Pool, cpu_count
import os
import time
def cpu_bound_task(n,i):
print(f'子进程: {os.getpid()} - 任务{i}')
while n > 0:
n -= 1
if __name__=='__main__':
print(f"CPU内核数:{cpu_count()}")
print(f'父进程: {os.getpid()}')
start = time.time()
p = Pool(4)
for i in range(2):
p.apply_async(cpu_bound_task, args=(100000000,i))
p.close()
p.join()
end = time.time()
print(f"耗时{end - start}秒")
输出:
CPU内核数:8
父进程: 18616
子进程: 21452 - 任务0
子进程: 16712 - 任务1
耗时5.928101301193237秒
IO 密集型任务:
def io_bound_task(self, n, i):
print(f'子进程: {os.getpid()} - 任务{i}')
print(f'IO Task{i} start')
time.sleep(n)
print(f'IO Task{i} end')
if __name__=='__main__':
print('主进程: {}'.format(os.getpid()))
start = time.time()
for i in range(2):
self.io_bound_task(4,i)
end = time.time()
print(f"耗时{end - start}秒")
输出:
主进程: 2780
子进程: 2780 - 任务0
IO Task0 start
IO Task0 end
子进程: 2780 - 任务1
IO Task1 start
IO Task1 end
耗时8.04494023323059秒
print(f"CPU内核数:{cpu_count()}")
print(f'父进程: {os.getpid()}')
start = time.time()
p = Pool(2)
for i in range(2):
p.apply_async(io_bound_task, args=(4, i))
p.close()
p.join()
end = time.time()
print(f"耗时{end - start}秒")
输出:
CPU内核数:8
耗时4.201171398162842秒
父进程: 1396
子进程: 2712 - 任务0
IO Task0 start
子进程: 10492 - 任务1
IO Task1 start
IO Task0 endIO Task1 end
可以看出对于IO密集型任务,Python多线程具有显著提升。
print(f'父进程: {os.getpid()}')
start = time.time()
p1 = Process(target=io_bound_task, args=(4, 1))
p2 = Process(target=io_bound_task, args=(4, 2))
p1.start()
p2.start()
p1.join()
p2.join()
end = time.time()
print("耗时{}秒".format((end - start)))
输出:
父进程: 12328
子进程: 12452 - 任务2
IO Task2 start
子进程: 16896 - 任务1
IO Task1 start
IO Task1 endIO Task2
end
耗时4.1241302490234375秒
IO型任务还可以使用协程,协程比线程更加轻量级,一个线程可以拥有多个协程,协程在用户态执行,完全由程序控制。一般来说,线程数量越多,协程性能的优势越明显。这里就不介绍Python协程了,下面Python代码是协程的其中一种实现方式:
import asyncio
import time
async def io_bound_task(self,n,i):
print(f'子进程: {os.getpid()} - 任务{i}')
print(f'IO Task{i} start')
# time.sleep(n)
await asyncio.sleep(n)
print(f'IO Task{i} end')
if __name__ == '__main__':
start = time.time()
loop = asyncio.get_event_loop()
tasks = [io_bound_task(4, i) for i in range(2)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
end = time.time()
print(f"耗时{end - start}秒")
输出:
子进程: 5436 - 任务1
IO Task1 start
子进程: 5436 - 任务0
IO Task0 start
IO Task1 end
IO Task0 end
耗时4.008626461029053秒
Python 由于GIL锁的存在,无法利用多进程的优势,要真正利用多核,可以重写一个不带GIL的解释器, 比如JPython(Java 实现的 Python 解释器)。
某些Python 库使用C语言实现,例如 NumPy 库不受 GIL 的影响。在实际工作中,如果对性能要求很高,可以使用C++ 实现,然后再提供 Python 的调用接口。另外Java语言也没有GIL限制。
对于多线程任务,如果线程数量很多,建议使用Python协程,执行效率比多线程高。
以上就是关于python多线程和多进程的介绍,希望对大家理解多线程和多进程有帮助,想要了解更多python多线程和多进程的内容,大家可以关注其他相关文章
文本转载自脚本之家
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
这篇文章主要为大家介绍了Python区块链块的添加教程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
内容介绍一、问题背景二、Canny算法(一)、高斯平滑(二)Sobel算子计算梯度(三)非极大化抑制(四)滞后边缘跟踪一、问题背景纸面上有一枚一元钱的银币,你能在Canny和Hough的帮助
Python基础学习之字符串操作 字符串是编程中最常遇到的类型,所以掌握好字符串的常用操作方法,非常的必要! 1. 字符串的切片 [字符串的切片是最常见的字符串操作,必须要掌握;
这篇文章主要介绍了是如何进行机器学习的模型的训练,全文逻辑清晰,简单易懂,如果您正在学习机器学习那么可以参考下,说不定会有不一样的收货
批量文件整理一直是日常工作中令人头疼的事,使用 Python 进行大批量文件整理,可以大大提升工作效率。本文主要介绍了利用Python实现文件的重命名和删除,感兴趣的小伙伴可以关注一下
成为群英会员,开启智能安全云计算之旅
立即注册关注或联系群英网络
7x24小时售前:400-678-4567
7x24小时售后:0668-2555666
24小时QQ客服
群英微信公众号
CNNIC域名投诉举报处理平台
服务电话:010-58813000
服务邮箱:service@cnnic.cn
投诉与建议:0668-2555555
Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 ICP核准(ICP备案)粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008