00.写在之前
大家好,我是 Rocky0429,今天我来写一下 Python 中的多线程。在正式开始之前,我先用比较通俗的语言给大家介绍几个比较重要的概念。
首先是「并发编程」。「并发」其实在我们的生活中随处可见,比如我们去银行存钱,银行如果只有一个窗口并且办业务的人又比较多,那么肯定就是排成一个长长的队伍,这样的体验对我们来说肯定是非常差的。那么该如何解决这个问题呢?那就是多开几个窗口,把人流分散开,这样就减短了我们队伍的长度,减少整体排队的时间。
上面是我们显示生活中遇到的问题,其实在计算机中我们也会遇到这样的问题,那么我们是怎么解决的呢?其实是这样,每次我们运行一个 Python 程序,这个运行中的程序我们称它为「进程」,如果我们想让它快一点,能够像我们上面例子中多开几个窗口并发解决问题一样,我们就要在进程里引入一个更小的东西,那就是「线程」。
我来举一个具体的例子,比如我们每天都会用的微信,每次运行它的时候会产生一个进程,估计大家都碰到过这种情况,就是你在和别人开视频的时候,另外有一个人给你发消息,我们都知道这个时候可以把视频的窗口缩小然后回复另一个人的消息,能完成这样的操作,其实就是微信里的多个线程帮助我们完成了这件事。
在编写 Python 程序的时候我们也会遇到这种“同时”的需求,同时有大量的请求过来,要我们同时对它们进行处理,那么这个处理的方法,就是「多线程」编程。
01.创建「线程」
Python 的标准库中自带了多线程相关的模块,使在 python 中创建线程成了一件很简单的事。与线程相关的模块一共有两个:thread 和 threading。一般情况下我们只需要 threading 即可。
下面我来写一个简单的使用多线程的例子:
import threading
def func():
print('Hello World')
def main():
for i in range(4):
t = threading.Thread(target=func)
t.start()
if __name__ == '__main__':
main()
上面的代码中,我定义了一个 func 函数,然后在 main 函数中通过 for 循环创建了 4 个线程,然后通过将 target = func 的方式去告诉线程执行 func 函数,一切就绪后调用线程的 start 方法运行线程。结果如下:
Hello World
Hello World
Hello World
Hello World
这个结果看起来和我们直接用 for 循环打印四次 Hello World 没什么区别,其实区别还是有的,就是肉眼看不出来而已,下面我来改造一下上面的程序:
import threading
import time
def func():
print('Hello World')
time.sleep(1)
def main():
for i in range(4):
t = threading.Thread(target=func)
t.start()
if __name__ == '__main__':
main()
上面我加了 time.sleep(1),如果只是用 for 循环的话,这个程序至少得运行 4 秒,但是由于我们用的是线程并发运行,其实整个程序只需要花费 1 秒多就可以运行完毕,你可以自行尝试一下,可以 sleep 的时间长点自行体验一下。
当然了,如果你自己不乐意动手,作为关爱读者成长协会的会长,我这还有一个好的办法,且待我再改造一下:
import threading
import time
from threading import current_thread
def func():
print(current_thread().getName(),'start')
print('Hello World')
time.sleep(1)
print(current_thread().getName(), 'end')
def main():
for i in range(4):
t = threading.Thread(target=func)
t.start()
if __name__ == '__main__':
main()
上面的改造中引用了 current_thread,对当前运行状态进行一个显示,你可以很好的看到线程在运行中的一些过程,运行结果如下所示:
Thread-1 start
Hello World
Thread-2 start
Hello World
Thread-3 start
Hello World
Thread-4 start
Hello World
Thread-1 end
Thread-4 end
Thread-3 end
Thread-2 end
02.线程传参
在上面创建线程的例子其实是过于简单了,在我们实际的编程中给程序传递参数是必不可少的,下面我在之前例子的基础上,写一个传递参数的例子:
import threading
def func(cnt, name):
for i in range(cnt):
print('Hello {}'.format(name))
def main():
names = ['Rocky', 'leey', 'cp3', 'chen']
for i in range(4):
t = threading.Thread(target=func, args=(10, names[i]))
t.start()
if __name__ == '__main__':
main()
上面的程序中,我让 func 接受了两个参数,在 main 函数中定义了一个 names 的列表,之后在创建线程的时候将 names 中的元素传递给不同的线程。由上可以看出在 Python 中线程传递参数也是一件很简单的事,传递的参数都是调用 args,通过元组的形式进行。
03.写在之后
其实很多人认为 Python 的多线程是一个相当“鸡肋”的东西,因为标准的 Python 系统中使用了 GIL(全局解释器锁),它的作用是避免 Python 解释器中的线程问题,这样造成了在任意时刻只有一个线程在执行 Python 代码,这样就“糟蹋”了计算机「多核」的特性。
诚然,“糟蹋”了多核,这样对 CPU 密集型的程序来说,Python 多线程确实没有什么提升,反而会更慢,但我们的程序其实也不是无时无刻在“动弹”的,它们也要等待资源的下载,等待文件的读写,等待用户的输入等等等等,这类操作我们统一称为 I/O 操作,对于这类,才是真正显示 Python 多线程能力的时候。