学习背景(优缺点)
优点:
-
提高效率、执行同样的任务、减少时间消耗
-
高并发、高扩展性、低成本
-
需线程上下文切换的开销
缺点:
-
编程复杂度会有所提高
-
无法利用多核资源、协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上. 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
什么是CPU密集型计算、IO密集型计算
类型 | 概念 | 特点 | 举例 |
---|---|---|---|
CPU密集型(CPU-bound) | CPU密集型也叫计算密集型,是指IP在很短的时间就可以完成,CPU需要大量的计算和处理,特点是CPU占用率很高 | 本地(CPU)计算 | 正则匹配、压缩解压缩、加密解密等 |
IO密集型(IO-bound) | IO密集型是指系统运作大部分的状况是在CPU在等待IO(内存/硬盘)读写造作时,占用率仍然较低 | 占用大量外部资源,如内存磁盘和网络 | 文件处理程序,网络爬虫程序,读写数据库程序 |
多线程、多进程、多协程的对比
三者关联:一个进程可启动N个线程,一个线程可以启动N个协程
三者优缺点:
类型 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
多进程 (python 的 multiprocessing模块) | 可利用多核CPU并行计算 | 占用资源比线程多,也是最多的,可启动数量比线程少 | CPU密集型计算 |
多线程 (python 的 threadin 模块) | 相比进程,更轻量级,占用资源少 | 1. 相比进程,python多线程只能并发执行,不能同时利用多CPU(GIL即全局解释器锁),只能同时使用一个CPU 2. 相比协程,启动数目有限制,占用内存资源,有线程切换开销 | IO密集型计算、同时运行的任务数目要求不多 |
多协程 (python 的 asynclo模块) | 内存开销最少,启用数量可以很多,一个线程中可以开几万个协程,但是只是占用的本身线程的资源(一个线程) | python支持的库有限制,例如python中使用多协程进行调用requests库是不支持的,这里要用 aiohttp 库,并且代码实现复杂 | IO密集型计算,需要超多任务运行,但有线程库支持的场景(基础之上) |
全局解释器锁(GIL)
python 速度慢的原因
原因一 | python为动态语言,边解释边执行(源码翻译为机器码) |
---|---|
原因二 | GIL无法利用多核CPU并发执行 |
GIL是什么
官方的解释:GIL是用来防止多线程并发执行机器码的一个Mutex(互斥锁),原因是:Cpython的内存管理是 not thread-safe
全局解释器锁:是计算机程序设计语言解释器用于同步线程的一种机制,他使得任何时刻仅有一个线程在执行。即便在多核心处理器上,使用GIL的解释器也只允许同一时间执行一个线程
如上图解释,多线程为流水线操作,其实可以说在有GIL的情况下,启用多个线程,同一时间还是进行的一个任务,并没有实质的并发操作,即使机器为多核CPU运转,在GIL的存在下,单个时刻也只能使用一个,相比并发加速的C++/Java(同一时刻可以并发执行) 慢很多
为什么要有GIL
Python设计初期,为了规避并发问题引入了GIL,但是现在想要去除,官方大大发现去除不掉了
为了解决多线程之间数据完整性和状态同步问题
原因详解:
Python中对象的管理,是使用与i你用计数器进行的,引用数为0则释放对象
假设,开了有两个线程,来处理对象obj,如图:
然而,GIL的出现解决了此类问题,简化了python对共享资源的管理
规避GIL带来的限制
多线程threading 机制依然是有用的,用于IO密集型计算
一、在IO操作期间(文件读写等),线程对释放GIL,实现CPU和IO的并行,但是多线程用于CPU密集型计算时,只会更加拖慢速度
二、使用multiprocessing 的多线程机制实现真正的并行计算,利用多核CPU优势
多线程使用举例
import threading
import requests
import time
def url_blog():
url_lst = []
for page in range(3):
url = f'https://www.cnblogs.com/#p{page}'
url_lst.append(url)
return url_lst
def blog(url):
response = requests.get(url).text
print(len(response))
def info_blog():
url_lst = url_blog()
treads = [] #创建泪飙存储url,方便后面为每个url创建多线程
for url in url_lst: #为每个url操作创建一个线程
treads.append(
threading.Thread(target=blog, args=(url,))
)
for tread in treads:
tread.start() #启动每个线程
for tread in treads:
tread.join() #结束每个线程
print("线程结束")
if __name__ == '__main__':
start_time = time.time()
info_blog()
end_time = time.time()
print(end_time-start_time)
愿爱无忧
end
原文始发于微信公众号(朱厌安全团队):Python并发编程--多线程
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论