threading
threading库是python的线程模型,利用threading库我们可以轻松实现多线程任务。
threading模块包含的类
包含常用的Thread,Queue,Lock,Event,Timer等类
threading模块常用方法
current_thread()
- threading.current_thread() : 返回当前的Thread类对象(线程对象)
在哪个线程中调用threading的current_thread方法就返回哪个线程。
import threading
# 在主线程中直接打印,可以看到返回主线程MainThread
print(threading.current_thread()) # <_MainThread(MainThread, started 22660)>
print(threading.current_thread().name) # MainThread
import threading
import time
# 创建子线程,在子线程中打印,打印的是正在执行的子线程
def task():
print('》》》线程:{thread}开始执行task'.format(thread=threading.current_thread().name))
time.sleep(1)
print('》》》线程:{thread}完成执行task'.format(thread=threading.current_thread().name))
t = threading.Thread(target=task, name='线程1')
t.start()
结果:
》》》线程:线程1开始执行task
》》》线程:线程1完成执行task
main_thread()
- threading.main_thread() : 返回当前主线程对象。即:启动python解释器的线程对象
import threading
import time
def task():
print('》》》线程:{thread}——开始执行task'.format(thread=threading.current_thread().name))
time.sleep(1)
print('》》》当前主线程:{thread}'.format(thread=threading.main_thread().name))
print('》》》线程:{thread}——完成执行task'.format(thread=threading.current_thread().name))
t = threading.Thread(target=task, name='线程1')
t.start()
结果
》》》线程:线程1——开始执行task
》》》当前主线程:MainThread
》》》线程:线程1——完成执行task
get_ident()
- threading.get_ident(): 返回当前线程的线程标识符。线程标识符是一个非负整数,并无特殊函数,只是用来标识线程,该整数可能会被循环利用。python3.3及以后版本支持该方法
import threading
import time
def task():
print('》》》线程号:{}——开始执行task'.format(threading.get_ident()))
time.sleep(5)
t1 = threading.Thread(target=task, name='线程1')
t1.start()
结果:
》》》线程号:35040——开始执行task
enumerate()
- threading.enumerate(): 返回当前处于alive状态的所有Thread对象的list
-alive状态指线程启动后、结束前,不包括启动前和终止后的线程。
import threading
import time
def task():
print('》》》线程:{}——开始执行task'.format(threading.current_thread().name))
time.sleep(5)
print('》》》线程:{}——完成执行task'.format(threading.current_thread().name))
t1 = threading.Thread(target=task, name='线程1')
t1.start()
t2 = threading.Thread(target=task, name='线程2')
t2.start()
t3 = threading.Thread(target=task, name='线程3')
t3.start()
print(threading.enumerate())
结果:
[<_MainThread(MainThread, started 25232)>, <Thread(线程1, started 32200)>, <Thread(线程2, started 13064)>, <Thread(线程3, started 10452)>]
active_count()
- threading.active_count() : 返回当前处于alive状态的线程个数
import threading
import time
def task():
print('》》》线程:{thread}——开始执行task'.format(thread=threading.current_thread().name))
time.sleep(5)
print('》》》线程:{thread}——完成执行task'.format(thread=threading.current_thread().name))
t1 = threading.Thread(target=task, name='线程1')
t1.start()
t2 = threading.Thread(target=task, name='线程2')
t2.start()
t3 = threading.Thread(target=task, name='线程3')
t3.start()
time.sleep(0.1)
print('》》》当前活跃线程数:{}'.format(threading.active_count()))
结果:
可以看到活跃线程数为4,包括:主线程,线程1,线程2,线程3
》》》线程:线程1——开始执行task
》》》线程:线程2——开始执行task
》》》线程:线程3——开始执行task
》》》当前活跃线程数:4
》》》线程:线程3——完成执行task
》》》线程:线程1——完成执行task
》》》线程:线程2——完成执行task
stack_size
- threading.stack_size([size]): 返回创建线程时使用的栈的大小。如果指定size参数,则用来指定后续创建的线程使用的栈大小,size必须是0(0表示使用系统默认值)或大于32K的正整数。
import threading
# 输出当前线程栈的大小
print(threading.stack_size()) # 0
# 设置后续线程栈的大小
threading.stack_size(43*1024) # 43*1024=44032
print(threading.stack_size()) # 44032
Thread类
Thread类是threading标准库中最重要的一个类。
你可以通过实例化Thread类,创建Thread对象,来创建线程。
你也可以通过继承Thread类创建派生类,并重写__init__
和run方法,实现自定义线程对象类。
创建线程
1.实例化Thread类
通过实例化Thread类,来创建线程,通过target传入函数,即线程需要做的任务。
Thread类的构造函数:
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None):
target:传入目标函数的名称
name:为线程名,一般命名格式为:Thread-N (N为small decimal number)
args:传入目标函数的参数,格式为元组。若目标函数需传关键字参数,可直接传入构造函数关键字组成的字典,无需传到任何参数。
daemon:标明此线程是否为守护线程(后文介绍),默认为False。
import threading
import time
def task(seconds):
print('》》》线程:{},开始执行task'.format(threading.current_thread().name))
time.sleep(seconds)
t1 = threading.Thread(target=task, args=(5,), name='Thread-1')
print(t1) # <Thread(Thread-1, initial)>
2.继承Thread类
创建子类继承Thread,通常重写run方法,在run方法写线程需要做的任务,然后实例化你创建的子类。
此方式具体原因请看下文的run方法。
启动线程
start()方法
调用Thread实例的start方法,会启动线程的活动。 每个线程对象最多只能调用一次。多次调用将引发RuntimeError错误。
线程启动后,将在子线程中,执行线程target传入的函数
import threading
import time
def task(seconds):
print('》》》线程:{},开始执行task'.format(threading.current_thread().name))
time.sleep(seconds)
print('》》》线程:{},结束执行task'.format(threading.current_thread().name))
t1 = threading.Thread(target=task, args=(1,), name='Thread-1')
t1.start() # 启动线程后,将在子线程中执行task函数
结果:
》》》线程:Thread-1,开始执行task
》》》线程:Thread-1,结束执行task
run()方法
用来实现线程的功能与业务逻辑,可以在子类中重写该方法来自定义线程的行为。
start()方法启动线程,实际上也是调用了run方法,在run方法中又调用了target传入的函数。
所以根本上,线程的功能和业务逻辑是在run方法去实现的,我们通过实例化Thread类target传入的功能函数也只是在run方法中去调了这个函数。
如果run方法没有被重写,并且target被定义,则会按照父类Thread的逻辑,直接调用实例化线程时传入target的方法,否则什么也不做。
如果你不想直接实例化Thread类,通过target传入线程的功能和业务逻辑,
你可以创建Thread的子类,且此子类的run方法被重写,启动此子类实例化的线程,则会直接调用重写的run方法的内容,target传入的函数则不会再被调用,除非你重写的run方法中人为调用了target传入的函数。所以在这种情况下,也不用再单独写个函数传入target。
import threading
import time
def task(seconds):
print('》》》线程:{},开始执行task'.format(threading.current_thread().name))
time.sleep(seconds)
print('》》》线程:{},结束执行task'.format(threading.current_thread().name))
class MyThread(threading.Thread):
def run(self) -> None:
print('run_test')
# Mythread类重写了run方法,则线程调用重写的run方法,target传入的函数将不起作用
t1 = MyThread(target=task, args=(1,), name='Thread-1')
t1.start()
结果:
run_test
综上,可以得出,如果你想创建线程:
1.可以直接通过创建实例化Thread类创建,并且通过target传入你想做的事情。
2.继承Thread类,重写run方法,在run方法中写你想做的事情,然后实例化你创建的子类。
线程执行顺序
在默认情况下,线程执行:
import threading
import time
def task(seconds):
print('》》》线程:{},开始执行task'.format(threading.current_thread().name))
while seconds > 0:
print('》》》线程:{0},倒计时:{1}秒'.format(threading.current_thread().name, seconds))
time.sleep(1)
seconds -= 1
print('》》》线程:{},结束执行task'.format(threading.current_thread().name))
t1 = threading.Thread(target=task, args=(2,), name='Thread-1')
t2 = threading.Thread(target=task, args=(2,), name='Thread-2')
t1.start()
t2.start()
print('》》》主线程打印:主线程的print')
print('》》》主线程打印:当前存活线程:{}'.format(threading.enumerate()))
结果:
》》》线程:Thread-1,开始执行task
》》》线程:Thread-1,倒计时:2秒
》》》线程:Thread-2,开始执行task
》》》主线程打印:主线程的print
》》》线程:Thread-2,倒计时:2秒
》》》主线程打印:当前存活线程:[<_MainThread(MainThread, started 19876)>, <Thread(Thread-1, started 24152)>, <Thread(Thread-2, started 35436)>]
》》》线程:Thread-1,倒计时:1秒
》》》线程:Thread-2,倒计时:1秒
》》》线程:Thread-1,结束执行task
》》》线程:Thread-2,结束执行task
由结果可以看出:
主线程代码顺序执行创建t1,t2两个Thread实例
子线程t1开始执行,并执行倒计时,遇到time的sleep阻塞时,释放GIL锁
此时,子线程t2开始执行
主线程的执行不受t1和t2两个子线程的影响,代码走到这,直接正常执行主线程打印
子线程t2执行倒计时,遇到time的sleep阻塞时,释放GIL锁,
主线程继续正常执行,打印当前存活线程。
子线程t2释放GIL锁之后,被子线程t1拿到,两者交替完成任务。
由以上可以看出:
1.主线程代码顺序执行,不会受子线程阻塞的影响。
2.主线程代码执行完毕后,会等待所有子线程执行完毕。
3.子线程遇到阻塞,会释放GIL锁,其他子线程继续执行,交替完成。
如果你想让子线程启动后,主线程代码阻塞,那么你可以使用子线程的join方法。
如果你想让主线程执行完毕后,不再等待未完成的子线程,可以将子线程设置为守护线程。
join()方法
Thread实例的join(timeout=None)方法用于阻塞主线程,可以想象成将某个子线程的执行过程插入(join)到主线程的时间线上,主线程的后续代码延后执行。
通俗地说就是是子线程告诉主线程,你要在设置join的位置等我执行完毕你再往下执行。
- 一个线程中调用另一个线程的join方法,调用者将被阻塞,直到被调用线程终止(正常退出或者抛出未处理的异常或者到达timeout的等待时间)。
- timeout参数指定调用者等待多久,如果没有设置超时,就一直等到被调用线程结束。
- 一个线程可以被join多次。
- join一定要在线程启动以后调用。
在上文线程执行顺序代码中,加入线程调用join方法:
import threading
import time
def task(seconds):
print('》》》线程:{},开始执行task'.format(threading.current_thread().name))
while seconds > 0:
print('》》》线程:{0},倒计时:{1}秒'.format(threading.current_thread().name, seconds))
time.sleep(1)
seconds -= 1
print('》》》线程:{},结束执行task'.format(threading.current_thread().name))
t1 = threading.Thread(target=task, args=(2,), name='Thread-1')
t2 = threading.Thread(target=task, args=(2,), name='Thread-2')
t1.start()
t2.start()
t1.join() # 主线程调用t1线程的join方法,那么主线程(调用者)将被阻塞,直到t1(被调用者)执行结束。
t2.join()
print('》》》主线程打印:主线程的print')
print('》》》主线程打印:当前存活线程:{}'.format(threading.enumerate()))
结果:
》》》线程:Thread-1,开始执行task
》》》线程:Thread-1,倒计时:2秒
》》》线程:Thread-2,开始执行task
》》》线程:Thread-2,倒计时:2秒
》》》线程:Thread-1,倒计时:1秒
》》》线程:Thread-2,倒计时:1秒
》》》线程:Thread-2,结束执行task
》》》线程:Thread-1,结束执行task
》》》主线程打印:主线程的print
》》》主线程打印:当前存活线程:[<_MainThread(MainThread, started 37320)>]
可以发现,主线程被阻塞掉,t1,t2线程执行完毕后,主线程打印函数才执行
daemon线程(守护线程)
上文提到,主线程代码执行完毕后,会等待所有子线程执行完毕,整个程序才退出。
因为子线程默认为【非守护线程】,主线程代码执行完毕,各子线程会继续运行,直到所有【非守护线程】结束,python程序退出。
如何理解守护线程:
是为守护别人而存在,当设置为守护线程后,被守护的主线程不存在后,守护线程也自然不存在,直接结束。
所以说,守护线程是不被考虑,爱允许到哪运行到哪,主线程不关心,不等待。可以理解为随时可以被终止的线程。
试用场景:
后台任务。如:发送心跳包,监控,这种场景最多
主线程工作时,才有用的线程。如:主线程中维护公共的资源,主线程已经清理了,准备退出,而工作线程使用这些资源工作也没有意义了,一起退出最合适。
注意
:
如果daemon线程使用了join方法,主线程仍然会被阻塞,等待此线程执行完毕后主线程才会退出,所以这个线程的守护就没有意义了,它仍然是主线程需要等待线程。
所以如果你想让主线程执行完毕后,不再等待未完成的子线程,可以将子线程设置为守护线程(避开使用join方法):
daemon=True
- 实例化Thread时,传入daemon为True,标明此线程为守护线程
t1 = threading.Thread(target=task, args=(2,), name='Thread-1', daemon=True)
setDaemon(daemonic)方法
调用线程实例的setDaemon(daemonic)方法,daemonic传入True或者False。可以在线程创建以后再设置线程为守护线程。
- 务必在线程start前进行设置,否则会报异常。
t2 = threading.Thread(target=task, args=(2,), name='Thread-2')
t2.setDaemon(True)
直接对其daemon属性进行复制也行
t2 = threading.Thread(target=task, args=(2,), name='Thread-2')
t2.daemon = True
让我们来看一下守护线程的效果:
import threading
import time
def task(seconds):
print('》》》线程:{},开始执行task'.format(threading.current_thread().name))
while seconds > 0:
print('》》》线程:{0},倒计时:{1}秒'.format(threading.current_thread().name, seconds))
time.sleep(1)
seconds -= 1
print('》》》线程:{},结束执行task'.format(threading.current_thread().name))
t1 = threading.Thread(target=task, args=(2,), name='Thread-1', daemon=True)
t2 = threading.Thread(target=task, args=(2,), name='Thread-2')
t2.setDaemon(True)
t1.start()
t2.start()
print('》》》主线程打印:主线程的print')
print('》》》主线程打印:当前存活线程:{}'.format(threading.enumerate()))
结果:
》》》线程:Thread-1,开始执行task
》》》线程:Thread-1,倒计时:2秒
》》》线程:Thread-2,开始执行task
》》》主线程打印:主线程的print
》》》线程:Thread-2,倒计时:2秒》》》主线程打印:当前存活线程:[<_MainThread(MainThread, started 33608)>, <Thread(Thread-1, started daemon 36944)>, <Thread(Thread-2, started daemon 36532)>]
从以上结果可以看出,主线程打印完毕后,程序直接退出,因为t1和t2线程都是守护线程,都不被主线程关心,不被主线程等待。
Thread实例的其他属性和方法
属性 | 含义 |
---|---|
name | 只是一个名字,只是个标识,可以重复 |
ident | 线程ID,它是非0整数,线程启动后才会有ID,否则为None,线程退出后此ID仍旧可以访问,此ID可以重复使用 |
daemon | 是否为daemon线程的布尔值 |
方法 | 含义 |
---|---|
is_alive() | 返回线程是否活着 |
isDaemon() | 返回线程是否为守护线程 |
getName() | 返回线程名。 |
setName() | 设置线程名。 |
多个线程创建使用
在需要创建多个线程时,可以将线程放入列表中,start,join方法都可以试用for循环进行调用。
第一个for循环同时启动了所有子线程,随后在第二个for循环中执行t.join() ,主线程实际被阻塞的总时长等于其中执行时间最长的一个子线程。
一定注意:所以线程必须全部start完毕,才可以进行join,所以start和join的for循环要分开写。
import threading
import time
def task(seconds):
print('》》》线程:{},开始执行task'.format(threading.current_thread().name))
time.sleep(1)
# while seconds > 0:
# print('》》》线程:{0},倒计时:{1}秒'.format(threading.current_thread().name, seconds))
# time.sleep(1)
# seconds -= 1
print('》》》线程:{},结束执行task'.format(threading.current_thread().name))
thread_list = []
for i in range(5):
t = threading.Thread(target=task, name="Thread-" + str(i + 1), args=(1,))
thread_list.append(t)
for thread in thread_list:
thread.start()
for thread in thread_list:
thread.join()
print('》》》主线程打印:主线程的print')
print('》》》主线程打印:当前存活线程:{}'.format(threading.enumerate()))
结果:
》》》线程:Thread-1,开始执行task
》》》线程:Thread-2,开始执行task
》》》线程:Thread-3,开始执行task
》》》线程:Thread-2,结束执行task
》》》线程:Thread-1,结束执行task
》》》线程:Thread-3,结束执行task
》》》主线程打印:主线程的print
》》》主线程打印:当前存活线程:[<_MainThread(MainThread, started 31036)>]
Process finished with exit code 0
Timer类
threading.Timer继承自Thread,所以就是线程类,具有线程的能力和特征,他的实例能够延时执行目标函数的线程,相当于一个定时器。
并且在到达既定时间真正执行目标函数之前,你都可以cancel,撤销执行。
实例创建:
threading.timer(interval, function, args=None, kwargs=None)
start方法执行以后,Timer对象处于等待状态,既定时间到后,开始执行function函数
import threading
def add(x, y):
print(x, y)
# 3秒钟之后执行线程
t = threading.Timer(3, add, args=(4, 5))
t.start()
print(threading.enumerate()) # [<_MainThread(MainThread, started 4301424000)>, <Timer(Thread-1, started 6184873984)>]
t.cancel()
只要这个Timer还没正式启动,还在等待,就可以使用cancel()方法停止掉
如果时间已经到了,函数已经开始执行了,那么这个cancel就没有任何效果了
import threading
import time
def add(x, y):
print(x, y)
t = threading.Timer(3, add, args=(4, 5))
t.start()
time.sleep(2)
t.cancel()
print('主线程打印')
以上代码在线程未到执行时间前,执行了cancel方法,进行了撤回,所以线程不会再启动。