查看原文
其他

Python多线程的一些知识

咪咪怪 咪哥杂谈 2019-10-31
咪哥杂谈

本篇阅读时间约为 6 分钟。


1

前言


了更好地体验多线程爬虫,本章先介绍下需要了解的知识点,以便后续的多线程爬虫文章有更好的理解与学习。


在接下来要讲的知识点中,感兴趣的读者们请先弄清楚进程和线程两者是什么?它们各自有着什么样的关系呢?读下廖雪峰老师简单介绍的例子,比喻非常生动清晰,故这里不多做讲解。


https://www.liaoxuefeng.com/wiki/1016959663602400/1017627212385376


看完后记住其中一个观念进程是由若干线程组成的,一个进程至少有一个线程。(面试中常会问到的知识点)


2

多线程和计算机 cpu 之间的联系


经过上面的概念介绍,大家想两个问题,为什么在编写代码的过程中要使用多进程或者多线程?


世界上第一台计算机被美国国防用来进行弹道计算随着时间消逝,到了 20 世纪 70 年代以后,微处理机的出现,使电子计算机的应用越来越广泛。 


同时,计算机本身的硬件也不断的发生了变化,比如早年的家用计算机只能存储 kb 单位的数据,而现在则可以存储 TB 以上的数据。又比如,计算机的核心组件 CPU 从原来的单核变为了多核。


图片来源 百度

中央处理器(CPU,Central Processing Unit)是一块超大规模的集成电路,是一台计算机的运算核心(Core)和控制核心( Control Unit)。它的功能主要是解释计算机指令以及处理计算机软件中的数据。

百度


笔者现在用的这台 windows 电脑,如何查看它的 cpu 核心数呢?


按下 win 键(在 alt 左边) + R ,打开运行窗口,输入 cmd



输入 wmic,回车:


再输入 cpu get *,回车:


拖拽滚动条,看到下图:


有 2 个核心,而笔者的 CPU 型号是:



回到刚才提的问题中,不论是多线程还是多进程,最终的目的都是为了加快我们程序的运行速度,提高效率,节省时间


假设有一家武器铺接到个订单,要打造 10 件武器。而打造武器需要铁匠,一个冶炼车间可以容纳 5 个铁匠进行冶炼。一个人打造一把武器用时 1 天。类比到计算机上, cpu 的核心数大家则可以想象成冶炼车间,有几个核心则有几个冶炼车间,线程数目可以想象成人数(老板与铁匠)。


武器铺老板(主线程)一共招收了 10 个铁匠小弟(子线程),但是早期的时候只有一个冶炼车间(cpu 核心数)。那么打造 10 件武器时,只能最多有 5 个人干活。第一天前 5 个铁匠干活,第二天后 5 个铁匠干活。这样加起来 10 把武器则需要 2 天才能完成。(单核心,多个线程在单个核心上间歇运行)


后来呢,皇城下的王子(订单主人)觉得武器店效率太慢!这样的效率不高,既然铁匠有 10 个人,那再造一间冶炼车间就好了呀!所以投资新建了一间冶炼车间。于是过些时日,武器店在接到打造 10 件武器时,只用了 1 天,10 个铁匠就打造了 10 把武器。(多核心,多个线程在多个的核心上高效运行)


通过这个看似恰当又不恰当的例子,其实想说的就是现在的 cpu 多核心时代,多线程/进程 可以尽量的充分利用每个核心,从而达到高效率执行任务。


3

Python 中的多线程


先来看看在 Python 中多线程如何使用?以上面打武器为例:


import threadingimport time
def work(): thread_name = threading.current_thread().name print(f'铁匠小弟 {thread_name} 正在拼命打造武器... ') n = 0 while n < 5: n = n + 1 print(f'铁匠小弟 {thread_name} 抡大锤: {n} 次') time.sleep(1) print(f'铁匠小弟 {thread_name} 打造武器完毕.')
def run(): thread_name = threading.current_thread().name # 获取当前线程名字 print(f'武器铺老板 {thread_name} 吆喝开工...')    t1 = threading.Thread(target=work, name='1 号')    t1.start()    t1.join() print(f'武器铺老板 {thread_name} 吆喝结束...') run()


结果如下:



解释下代码,在 Python 中,官方提供了一个自带的 threading 包,使用它可以来手动创建线程。


首先调用 run 函数,在 run 函数里面,先获取主线程的名字。然后武器店老板吆喝小弟 1 号开工。通过 threading.Thread 创建一个新的子线程并将其赋值为 t1,而这个子线程的任务是什么呢?它的 target 参数指向要运行的函数,也就是 work。name参数是给子线程命名。


work 具体就是铁匠小弟抡大锤,打造武器。随后回到 run 函数,使用 t1.start() 和 t1.join(),直至最终结束。这里大家就要养成查看官方文档的好习惯啦。打开 python 中文官方文档,搜索 thread,打开后,可以看到相应的方法介绍。


start、join、name详细介绍都在下方。




现在武器铺老板只召唤了 1 个小弟,那么在增加一个小弟呢?只需要改动下代码:


def run(): thread_name = threading.current_thread().name # 获取当前线程名字 print(f'武器铺老板 {thread_name} 吆喝开工...') t1 = threading.Thread(target=work, name='1 号')    t2 = threading.Thread(target=work, name='2 号')   # 多的小弟 t1.start() t2.start() # 多的小弟 t1.join()    t2.join()  # 多的小弟 print(f'武器铺老板 {thread_name} 吆喝结束...')


结果:




笔者运行此程序一共三次,不难发现,三次输出的结果都是不一样的。怎么理解这个结果呢?


这就有点像抢冶炼车间的大喇叭,用来汇报自己的功劳,大家都喜欢抢功劳。假设现在铁匠小弟抡锤子一下,就需要喊一声:“我(比如是铁匠 1 号)抡大锤xx次了!” 由于每个冶炼车间的大喇叭只有一个,所以几号小弟先抢到,几号就能用喇叭喊出来!


在计算机中,每个线程都会去抢夺 cpu 的资源,谁抢到了,谁就可以“发声”!所以有时候是铁匠小弟 1 号先打印输出,有时候是铁匠小弟 2 号先打印输出。



以上就是在 Python 中如何编写一个简单的多线程例子。而对于该使用线程还是进程,在不同的场景则不尽相同。廖雪峰老师比较过二者,依然是给出文章地址:


https://www.liaoxuefeng.com/wiki/1016959663602400/1017631469467456


略微熟悉 Python 多线程的同学,应该都听说过“全局锁”的概念。正是这把"锁",让 Python(CPython 解释器下,有些 Python 解释器并没有) 在多线程的情况下有了效率低下之说。具体的就不给大家解释了,因为这把“锁”不是一言两语能说清楚的。。感兴趣的可以自行搜索。


4

结语


本章是为了给后续文章做一个铺垫。当然其中有些点可能比较难理解,如果有哪里不太懂或者笔者表达不对的地方,欢迎大家留言指出,共同交流





▼往期精彩回顾▼Python 100天从新手到大师web scraper 插件爬取知乎文章标题总有那么几个人优秀到让你窒息(胡歌)


长按关注

公众号名称:咪哥杂谈

一个咪咪怪的公众号

长按二维码关注哦!


你点的每个在看,我都认真当成了喜欢


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存