5.6 Python 多线程的问题
你好,我是悦创。由于 Python 中 GIL 的限制,导致不论是在单核还是多核条件下,在同一时刻只能运行一个线程,导致 Python 多线程无法发挥多核并行的优势。
GIL 全称为 Global Interpreter Lock,中文翻译为全局解释器锁,「其最初设计是出于数据安全而考虑的。」
在 Python 多线程下,每个线程的执行方式如下:
- 获取 GIL
- 执行对应线程的代码
- 释放 GIL
可见,某个线程想要执行,必须先拿到 GIL,我们可以把 GIL 看作是通行证,并且在一个 Python 进程中,GIL 只有一个。拿不到通行证的线程,就不允许执行。这样就会导致,即使是多核条件下,一个 Python 进程下的多个线程,同一时刻也只能执行一个线程。
不过对于爬虫这种 IO 密集型任务来说,这个问题影响并不大。而对于计算密集型任务来说,由于 GIL 的存在,多线程总体的运行效率相比可能反而比单线程更低。
5.7 避免 GIL
前面开头已经提到:因为 「GIL」 的存在,所以不管我们开了多少线程,同一时间始终只有一个线程在执行。那我们该如何避免 「GIL」 呢?
那这样的话,我们「不开线程」不就行,(它的的存在已经无法避免,那我们选择不使用它不就相当于不存在嘛)。那这是,你会想:那不开线程我们开啥呢?
问的好!
我们来开:「进程」,那怎么说?别急!请听我细细道来。
比方你有 「3 个 CPU」(当然,你可能有更多,这里就按 「3」 个 「CPU」来为例子),那我们就开 「3 个进程」就好。一个 「CPU」 上运行就好。
「Ps:我们的进程是可以同时运行的。」
我们可以看一下下面的图片:
「任务管理器」
我们 「任务管理」 上的每一项都是一个进程。
多进程比多线程不好的地方是什么呢?
❝多进程的创建和销毁开销也会更大,成本高。
你可能线程可以开许多的线程,但你的进程就是看你的 「CPU」 数量。
❞
❝进程间无法看到对方数据,需要使用栈或者队列进行获取。
每个进程之间都是独立的。
就好像我们上面的谷歌浏览器和我们的 Pycharm 是没有任何关系的,谷歌浏览器上面的数据肯定不可能让 Pycharm 看到。这就是我们所说的进程之间的独立性。
如果你想要一个进行抓取数据,一个进行调用数据,那这时是不能直接调用的,需要你自己定义个结构才能使用。>>> 编程复杂度提升。
❞
❝学员问题:任务管理器上面超过五六个进程。都是进程的话,怎么能开那么多呢?
答:我们一个 CPU 不止能执行一个进程,就比如我的一个 CPU 里面密麻麻有许多进程。(比方我现在开六个进程)并发执行的。只不过计算机执行的速度非常快,这里我简单讲一下哈。这是计算机原理的课。
不管是任何操作系统,现在就拿单核操作系统来说:我们假设现在只有一个 CPU ,一个 CPU 里面六个「进程」,同一时间它只有一个进程在运行。不过我们计算执行速度非常快,这个程序执行完,它就会执行一个「上下文切换」,执行下一个。(因为,它执行的速度非常快,你就会感觉是并发执行一样。)
实际上,一个 CPU 同一时间只有一个进程在执行,一个进程里面它只有一个线程在执行。(当然,这个单核是五六年前了。现在肯定至少有双核。
那就说有第二个 CPU 了。
而第二个和 CPU 上面又有许多个 「进程」,两个 「CPU」 是互不相干。
那这时候,第一个 CPU 上面运行一个进程,而我们的第二个 CPU 上面也有一个进程,两个是互补相干。 (就相当于你开了两台电脑。)
但是同一个 CPU 在同一时间只有一个就进程。(不管你(电脑)速度多么快,实际上本质上(在那一秒)只有一个进程在执行。「如果你是双核,那就有两个进程。(四核就有四个进程)」
❞
Python 有个不好的地方,刚刚上面讲到,如果我们有两个 CPU 那就有两个进程在执行(那四个 CPU 就是四个进程在执行),**但是因为 Python 当中存在着 GIL,它即使有四个 CPU 每次也只有一个线程能进去,**也就是说:同一时间当中,一个 CPU 上的一个进程中的一个线程在执行。剩下的都不能运行,我们的 Python 不能利用多核。
如果,大家用的是 C、Java、Go 这种的就没有这个说法了。
5.8 线程池
我找了许多包,这个包还是不错的:Pip install threadpool
# project = 'Code', file_name = '线程池', author = 'AI悦创'
# time = '2020/3/3 0:05', product_name = PyCharm
# code is far away from bugs with the god animal protecting
# I love animals. They taste delicious.
import time
import threadpool
# 执行比较耗时的函数,需要开多线程
def get_html(url):
time.sleep(3)
print(url)
# 按原本的单线程运行时间为:300s
# 而多线程池的化:30s
# 使用多线程执行 telent 函数
urls = [i for i in range(100)]
pool = threadpool.ThreadPool(10) # 建立线程池
# 提交任务给线程池
requests = threadpool.makeRequests(get_html, urls)
# 开始执行任务
for req in requests:
pool.putRequest(req)
pool.wait()