什么是多线程?
线程(thread)是操作系统中能够进行运算的最小单位,包含于进程之中,一个进程可以有多个线程,这意味着一个进程中可以并发多个线程,即为多线程。
对于一个python程序,如果需要同时大量处理多个任务,有使用多进程和多线程两种方法。在python中,实现多线程主要通过threading模块,而多进程主要通过multiprocessing模块。
这两个模块的主要区别是:threading模块基于线程,而multiprocessing模块基于进程。threading模块使用共享内存来实现多线程,所有线程都共享一样的变量(这点在后续的实例中可以感受到);而multiprocessing基于子进程,每个子进程之间都有独立的变量和数据结构。两者的区别意味着threading更使用于I/O密集型任务(例如需要进行多表格读取操作),multiprocessing模块更适用于包含较多计算的CPU密集型任务(矩阵运算,图片处理类任务)。
需要注意的是,由于python中的GIL锁的存在,Python解释器只允许一个Python进程使用,这意味着对于一个解释器只允许一个进程在运行,这也是为什么threading模块无法适用于CPU密集型这类需要大量CPU资源的任务,因为一个进程的CPU资源有限,无论开启多少个线程,总的资源就只有那些,总耗时不会有太大变化。而multiprocessing模块则可以开多个进程,能够更快速的处理CPU密集型任务。
关于GIL锁和Multiprocessing模块的部分就不继续深入介绍了,本次主要介绍如何使用threading模块实现多线程的相关内容。
线程完整生命周期
一个线程完整的生命周期包括新建——就绪——运行——阻塞——死亡。
- 新建:即新创建一个线程对象
- 就绪:调用start方法后,线程对象等待运行,什么时候开始运行取决于调度
- 运行:线程处于运行状态
- 阻塞:处于运行状态的线程被堵塞,通俗理解就是被卡住了,可能的原因包括但不限于程序自身调用sleep方法阻塞线程运行,或调用了一个阻塞式I/O方法,被阻塞的进程会等待何时解除阻塞重新运行
- 死亡:线程执行完毕或异常退出,线程对象被销毁并释放内存
主线程与子线程
我们讲的多线程实际上指的就是只在主线程中运行多个子线程,而主线程就是我们的python编译器执行的线程,所有子线程和主线程都同属于一个进程。在未添加子线程的情况下,默认就只有一个主线程在运行,他会将我们写的代码从开头到结尾执行一遍,后文中我们也会提到一些主线程与子线程的关系。
不扯那么多概念了,接下来直接进入正题!
实例1:直接使用Thread创建线程对象
Thread类创建新线程的基本语法如下:
Newthread = Thread(target=function, args=(argument1,argument2,...))
- Newthread: 创建的线程对象
- function: 要执行的函数
- argument1,argument2: 传递给线程函数的参数,为tuple类型
假设一个任务task(当然task可以替换为其他任何任务,本实例中仅为假设),这个任务实现的功能是每隔1s打印某个字母,我们使用两个子线程,分别同时打印不同的字母a和b,实例如下:
"""
<case1: 直接使用threading中的Thread类创建线程>
Date: 2024/5/15
Author: 猫猫不吃sakana
"""
from threading import Thread
import time
from time import sleep
# 自定义的函数,可以替换成其他任何函数
def task(threadName, number, letter):
print(f"【线程开始】{threadName}")
m = 0
while m < number:
sleep(1)
m += 1
current_time = time.strftime('%H:%M:%S', time.localtime())
print(f"[{current_tim