05-5 万字长文:实现多线程(上)

2021/6/12 18:21:53

本文主要是介绍05-5 万字长文:实现多线程(上),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

5. Python 实现多线程

你好,我是悦创。在 Python 中,实现多线程的模块叫作 threading,是 Python 自带的模块。下面我们来了解下使用 threading 实现多线程的方法。

在具体实现之前,我们先来测试一下多线程与当线程裸奔的速度对比,为了更加直观,我这里使用把每种线程代码单独写出来并做对比:

「单线程裸奔:(这也是一个主线程(main thread))」

import time

def start():
for i in range(1000000):
		i += i
return

# 不使用任何线程(裸着来)
def main():
	start_time = time.time()
for i in range(10):
		start()
	print(time.time()-start_time)
if __name__ == '__main__':
	main()

「输出:」

6.553307056427002

「注意:因为每台电脑的性能不一样,所运行的结果也相对不同(请按实际情况分析)」


「接下来我们写一个多线程」

我们先创建个字典 「(thread_name_time)」 来存储我们每个线程的名称与对应的时间

import threading,time

def start():
for i in range(1000000):
		i += i
return

# # 不使用任何线程(裸着来)
# def main():
# 	start_time = time.time()
# 	for i in range(10):
# 		start()
# 	print(time.time()-start_time)
# if __name__ == '__main__':
# 	main()

def main():
	start_time = time.time()
	thread_name_time = {}# 我们先创建个字典 (thread_name_time) 用来来存储我们每个线程的名称与对应的时间

for i in range(10):
# 也就是说,每个线程顺序执行
		thread = threading.Thread(target=start)# target=写你要多线程运行的函数,不需要加括号
		thread.start()# 上一行开启了线程,这一行是开始运行(也就是开启个 run)
		thread_name_time[i] = thread # 添加数据到我们的字典当中,这里为什么要用i做key?这是因为这样方便我们join


for i in range(10):
		thread_name_time[i].join()
# 	join() 等待线程执行完毕(也就是说卡在这里,这个线程执行完才会执行下一步)
	print(time.time()-start_time)

if __name__ == '__main__':
	main()

「输出」

6.2037984102630615
# 6.553307056427002 裸奔
# 6.2037984102630615 单线程顺序执行
# 6.429047107696533 线程并发

我们可以看到,速度上的区别不大。

多线程并发不如单线程顺序执行快

这是得不偿失的

造成这种情况的原因就是 GIL

这里是计算密集型,所以不适用

在我们执行加减乘除或者图像处理的时候,都是在从 「CPU」 上面执行才可以。Python 因为 GIL 存在,同一时期肯定只有一个线程在执行,这样这样就是造成我们开是个线程和一个线程没有太大区别的原因。

而我们的网络爬虫大多时候是属于 「IO」 密集与计算机密集

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

BIOS:B:Base、I:Input、O:Output、S:System

也就是你电脑一开机的时候就会启动。

「1. 计算密集型」

在上面的时候,我们开启了两个线程,如果这两个线程要同时执行,那同一时期 「CPU」 上只有一个线程在执行。

那从上图可知,那这两个线程就需要频繁的在上下文切换。

Ps:我们这个绿色表示我们这个线程正在执行,红色代表阻塞。

所以,我们可以明显的观察到,线程的上下文切换也是需要消耗资源的(时间-ms)不断的归还和拿取 「GIL」 等,切换上下文。明显造成很大的资源浪费。

「2. IO 密集型」

我们现在假设,有个服务器程序(「Socket」)也就是我们新开的一个程序(也就是我们网络爬虫的最底层)开始爬取目标网页了,我们那个网页呢,有两个线程同时运行,我们线程二已经请求成功开始运行了,也就是上图的 (「Thread 2」)绿色一条路过去。

而我们的线程一(「Thread 1」)- 「Datagram」(这里它开启了一个 「UDP」),然后等待数据建立(也就是等待哪些 「HTML、CSS 等数据返回」)也就是说,在 **Ready to receive(recvfrom)**之间都是准备阶段。这样就是有一段时间一直阻塞,而我们的线程二可以一直无停歇也不用切换上下文就一直在运行。「这样的 IO 密集型就有很大的好处」。

IO 密集型,这样就把我们等待的时间计算进去了,节省了大部分时间。
这里我们需要注意的是,我们的多线程是运行在 IO 密集型上的,我们得区分清楚。

还有就是,资源等待,比如有时候我们使用浏览器发起了一个 「Get」 请求,那浏览器图标上面在「转圈圈」的时候就是我们请求资源等待的时间,(也就是图上面的 「Datagram 到 Ready to receive」 )数据建立到数据接收(就是转圈圈的时间)。我们完全就不需要执行它,就让它等待就好。「这个时候让另一个线程去执行就好」

换言之就是:第一个线程,我们爬取那个网页转圈圈的时候让另一个线程继续爬取。这样就避免了资源浪费。(把时间都利用起来)

「注意:」 请求资源是不需要 「CPU」 进行计算的,「CPU」 参与是很少的,而我们第一个例子,计算数字的 「for」 循环中,是需要 「CPU」 进行计算的。


watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

 



这篇关于05-5 万字长文:实现多线程(上)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程