python并发编程笔记2
是根据蚂蚁学Python的视频做的笔记,方便自己后续回顾
视频链接:BV1bK411A7tV
老师的源码
这一份笔记对应的是视频的P3-P4
文章目录
P3-Python速度慢的罪魁祸首,全局解释器锁GIL
1、Python速度慢的两大原因
-
原因1:
动态类型语言:
Python的变量既可以是数字,也可以随时切换成字符串,导致运行时随时检测类型
边解释边执行:
C语言直接编译成机器码,直接运行非常快,而Python执行就是源码,存在一个源码到机器码的翻译过程
-
原因2:
GIL:
它的存在,使Python无法利用多核CPU并发执行
2、GIL是什么?
全局解释器锁〔英语:Global Interpreter Lock,缩写GIL)
是计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行。
即便在多核心处理器上,使用GIL的解释器也只允许同一时间执行一个线程。
下图就是一个例子:
三个线程Thread1、2、3,哪个线程持有GIL,该线程才在执行,当遇到I/O时,就会释放GIL,I/O是指读写操作、发送网络等等
Thread1持有GIL,遇到I/O,释放GIL,这时Thread2才能开始进入运行状态
由于GIL的存在,即使电脑有多核CPU单个时刻也只能使用1个,相比并发加速的C++/JAVA所以慢。
单个时刻单个进程只能使用1个线程,但多进程下就可以有。
总结:(Python是一门伪多线程语言)
3、为什么有GIL这个东西?
GIL存在目的:为了解决多线程之间数据完整性和状态同步问题
GIL存在原因:Python中对象的管理,是使用引用计数器进行的,引用数为0则释放对象**(涉及到Python的垃圾回收)**
举例:开始:线程A和线程B都引用了对象obj,obj.ref_num 2,线程A和B都想撤销对obj的引用
若其中发生多线程调度切换,进行了两次对象释放,其导致的结果:发生报错,可能错释放别的内存,破坏内存。
GIL的引入就是解决以上类似的问题:简化了Python对共享资源的管理
4、怎样规避GIL带来的限制?
1、多线程threading用于IO密集型计算,不用于CPU密集型计算
因为在I/O (read,write,send,recv,etc.)期间,线程会释放GIL,实现CPU和IO的并行因此多线程用于IO密集型计算依然可以大幅提升速度
用于CPU密集型计算时,只会更加拖慢速度(只有一个cpu在运行,切多线程切换会增加开销)
2、使用multiprocessing的多进程机制实现并行计算、利用多核CPU优势
为了应对GIL的问题,Python提供了multiprocessing
P4-使用多线程,Python爬虫被加速10倍
1、Python创建多线程的方法
1、准备一个函数
def my_func(a, b):
do_craw(a, b)
2、怎样创建一个线程
import threading
t = threading.Thread(target=my_func, args=(100, 200))
3、启动线程
t.start()
4、等待结束
t.join()
2、改写爬虫程序,变成多线程爬取
# blog_spider.py
import requests
# 注意这里不要使用老师提供的这个(可能加入了防爬),否则你后面的爬取的永远只有第一页的数据
# urls = [f"https://www.cnblogs.com/#p{page}" for page in range(1, 51)]
# 请将urls改成这个
urls = [
f"https://www.cnblogs.com/sitehome/p/{page}"
for page in range(1, 50 + 1)
]
def craw(url):
r = requests.get(url)
print(url, len(r.text))
# craw(urls[0])
# 01multi_thread_craw.py
import blog_spider
import threading
import time
# 单线程代码
def single_thread():
print("single_thread begin")
for url in blog_spider.urls:
blog_spider.craw(url)
print("single_thread end")
# 多线程代码
def multi_thread():
print("multi_thread begin")
# 创建一个空列表,将每一个创建好的线程对象存进这个列表当中
threads = []
# 对每一个URL创建一个线程
for url in blog_spider.urls:
threads.append(
threading.Thread(target=blog_spider.craw, args=(url,))
)
# 对每个线程进行启动
for thread in threads:
thread.start()
for thread in threads:
thread.join()
print("multi_thread end")
if __name__ == '__main__':
start = time.time()
single_thread()
end = time.time()
print("single_thread cost:", end - start, "seconds")
start = time.time()
multi_thread()
end = time.time()
print("multi_thread cost:", end - start, "seconds")
3、速度对比:单线程爬虫VS多线程爬虫
single_thread cost: 8.126458168029785 seconds
multi_thread cost: 0.3084845542907715 seconds
8.12/3
2.7066666666666666
d cost:", end - start, “seconds”)
## 3、速度对比:单线程爬虫VS多线程爬虫
single_thread cost: 8.126458168029785 seconds
multi_thread cost: 0.3084845542907715 seconds
8.12/3
2.7066666666666666