目录
1.守护线程
(1)setDaemon
(2)通过daemon参数设置守护线程,daemon默认是None.
2.线程锁
(1)互斥锁(Lock),同一时刻仅能有一个访问者对其进行访问.
(2) 重入锁、也叫递归锁(RLock),互斥锁的升级版
(3)条件锁(Condition),递归锁的升级版
(4)事件锁(Event),条件锁的升级版
3.信号量(Semaphore),也是一种锁(条件锁的升级版),控制n个线程同时运行
4.队列(queue模块),用于线程间通信
使用setDaemon把子线程设置为主线程的守护线程,当主线程结束时,子线程就会跟着结束。
作用:守护线程是为其他线程提供服务。如果其他线程被杀死了,那么守护线程也就没有了存在的必要。
import time
from threading import Thread, current_thread
def demo():
print(f'子线程开始, 线程名字: {current_thread().name}')
time.sleep(2) # 模拟等待操作, 模拟耗时的io任务
# current_thread函数, 返回当前线程对象, 调用name方法输出当前线程名
print(f'子线程结束, 线程名字: {current_thread().name}')
if __name__ == '__main__':
print('主线程开始...')
threads = [Thread(target=demo) for _ in range(3)] # 这里是创建3个线程,放到一个列表里
for t in threads:
t.setDaemon(True) # 设置子线程为守护线程
t.start() # 启动线程
# for t in threads:
# t.join() # 线程等待
print('主线程结束...')
执行结果如下:可以看到,主线程线束后,所有子线程也结束了
如果子线程同时存在守护线程和非守护线程,这个时候因为非守护线程还在继续执行,守护线程也会继续执行。而不是主线程结束后,守护线程结束,非守护线程继续执行,这是不对的。
import time
from threading import Thread, current_thread
def demo():
print(f'子线程开始, 线程名字: {current_thread().name}')
time.sleep(2) # 模拟等待操作, 模拟耗时的io任务
# current_thread函数, 返回当前线程对象, 调用name方法输出当前线程名
print(f'子线程结束, 线程名字: {current_thread().name}')
if __name__ == '__main__':
print('主线程开始...')
threads = [Thread(target=demo) for _ in range(3)] # 这里是创建3个线程,放到一个列表里
n = 0
for t in threads:
n += 1
if n == 1:
t.setDaemon(True) # 将其中一个子线程设置为守护线程
t.start() # 启动线程
# for t in threads:
# t.join() # 线程等待
print('主线程结束...')
执行结果:
PS:将子线程设置为守护线程必须在调用start()方法之前,否则回引发RuntimeError异常
import time
from threading import Thread, current_thread
def demo():
print(f'子线程开始, 线程名字: {current_thread().name}')
time.sleep(2) # 模拟等待操作, 模拟耗时的io任务
# current_thread函数, 返回当前线程对象, 调用name方法输出当前线程名
print(f'子线程结束, 线程名字: {current_thread().name}')
if __name__ == '__main__':
print('主线程开始...')
threads = [Thread(target=demo, daemon=True) for _ in range(3)] # daemon=True, 设置子线程为守护线程
for t in threads:
t.start() # 启动线程
# for t in threads:
# t.join() # 线程等待
print('主线程结束...')
执行结果:
如果子线程同时存在守护线程和非守护线程, 结果同setDaemon。
import time
from threading import Thread, current_thread
def demo():
print(f'子线程开始, 线程名字: {current_thread().name}')
time.sleep(2) # 模拟等待操作, 模拟耗时的io任务
# current_thread函数, 返回当前线程对象, 调用name方法输出当前线程名
print(f'子线程结束, 线程名字: {current_thread().name}')
if __name__ == '__main__':
print('主线程开始...')
threads1 = Thread(target=demo, daemon=True) # daemon=True, 设置子线程为守护线程
threads2 = Thread(target=demo) # daemon默认为None
threads1.start()
threads2.start()
print('主线程结束...')
执行结果:
PS: setDaemon方法也是通过设置daemon属性来设置守护线程
先看一个例子,假设有一个数字,实始是0,一个线程做加法,每次加1,加一百万次,同时,一个线程做减法,每次减1,减一百万次,做完后这个数字应该是0。
from threading import Thread, current_thread, Lock
NUM = 0 # 账户的余额为0
def add():
global NUM
for i in range(1000000):
NUM += 1
def sub():
global NUM
for i in range(1000000):
NUM -= 1
if __name__ == '__main__':
threads_add = Thread(target=add)
threads_sub = Thread(target=sub)
threads_add.start()
threads_sub.start()
threads_add.join()
threads_sub.join()
print(f'最终账户余额: {NUM}')
执行结果:
在同一个进程中的多线程是共享资源的,即共享全局变量,线程之间也是进行随机调度,每次做加、减时,取到的NUM是不定的,随机的,所以会导致结果出现异常,此时需要引入锁,来保证线程安全。
锁的意思是,在同一时间内,只有一个线程可以操作全局变量,即A取到NUM时,B会处于等待状态,当A交出NUM时,B才能取到NUM。
from threading import Thread, Lock
NUM = 0 # 账户的余额为0
def add(lock):
global NUM
for i in range(1000000):
lock.acquire() # 获取锁
NUM += 1
lock.release() # 释放锁
def sub(lock):
global NUM
for i in range(1000000):
lock.acquire() # 获取锁
NUM -= 1
lock.release() # 释放锁
if __name__ == '__main__':
lock = Lock()
threads_add = Thread(target=add, args=(lock, ))
threads_sub = Thread(target=sub, args=(lock, ))
threads_add.start()
threads_sub.start()
threads_add.join()
threads_sub.join()
print(f'最终账户余额: {NUM}')
执行结果:
PS: 对于互斥锁来说,一次acquire()必须对应一次release(), 不然会引起死锁造成程序的阻塞,程序完全不动。
如下图,lock.acquire() 获取锁后没有做lock.release()释放锁,形成死锁。
支持使用with语句来实现加锁、解锁。
from threading import Thread, Lock
NUM = 0 # 账户的余额为0
def add(lock):
global NUM
with lock:
for i in range(1000000):
NUM += 1
def sub(lock):
global NUM
with lock:
for i in range(1000000):
NUM -= 1
if __name__ == '__main__':
lock = Lock()
threads_add = Thread(target=add, args=(lock, ))
threads_sub = Thread(target=sub, args=(lock, ))
threads_add.start()
threads_sub.start()
threads_add.join()
threads_sub.join()
print(f'最终账户余额: {NUM}')
跟互斥锁一样,区别在于以下2个方面,
1.使用时,可以多次加锁,多次解锁,但是加锁次数与解锁次数要保持一致,如果
acquire与release次数不一致,会导致死锁。
from threading import Thread, RLock
NUM = 0 # 账户的余额为0
def add(lock):
global NUM
for i in range(1000000):
lock.acquire() # 获取锁
lock.acquire() # 获取锁
NUM += 1
lock.release() # 释放锁
lock.release() # 释放锁
def sub(lock):
global NUM
for i in range(1000000):
lock.acquire() # 获取锁
lock.acquire() # 获取锁
NUM -= 1
lock.release() # 释放锁
lock.release() # 释放锁
if __name__ == '__main__':
lock = RLock()
threads_add = Thread(target=add, args=(lock, ))
threads_sub = Thread(target=sub, args=(lock, ))
threads_add.start()
threads_sub.start()
threads_add.join()
threads_sub.join()
print(f'最终账户余额: {NUM}')
2.可重入,RLock多用于这种函数嵌套的情况,因为RLock可以多次获取锁,如果用Lock是会报错的。
from threading import Thread, RLock
NUM = 0 # 账户的余额为0
def add(lock):
global NUM
for i in range(1000000):
lock.acquire() # 获取锁
lock.acquire() # 获取锁
NUM += 1
lock.release() # 释放锁
lock.release() # 释放锁
sub(lock)
def sub(lock):
global NUM
for i in range(1000000):
lock.acquire() # 获取锁
lock.acquire() # 获取锁
NUM -= 1
lock.release() # 释放锁
lock.release() # 释放锁
if __name__ == '__main__':
lock = RLock()
threads_add = Thread(target=add, args=(lock, ))
threads_sub = Thread(target=sub, args=(lock,))
threads_add.start()
threads_sub.start()
threads_add.join()
threads_sub.join()
print(f'最终账户余额: {NUM}')
PS:RLock同样支持使用with语句
条件锁是在递归锁的基础上增加了能够暂停线程运行的功能, Condition构造方法有个参数lock,可以不传,默认使用RLock()
使用也是比较简单,见以下范例。
Condition().wait() 挂起线程,直到满足条件再放行
Condition().notify(num) 放行n个挂起来的线程
Condition().notify_all() 放行所有挂起来的线程
from threading import Thread, RLock, Condition, current_thread
NUM = 0 # 初始账户的余额为0, 账户余额大于0才能做减法
def add(lock):
global NUM
for i in range(1001):
lock.acquire() # 获取锁
NUM += 1
if NUM > 0:
print(f'Num大于0, 可以放行了, Num: {NUM}')
lock.notify_all() # 放行
lock.release() # 释放锁
print(f'add, Num: {NUM}')
print(f'add....done... 线程: {current_thread().name}')
def sub(lock):
global NUM
for i in range(1000):
lock.acquire() # 获取锁
if NUM <= 0:
print(f'\nsub, NUM小于等于0, 挂起等待, 线程:{current_thread().name}, NUM: {NUM}')
lock.wait() # 如果NUM小于等于0则线程挂起等待
NUM -= 2
lock.release() # 释放锁
print(f'sub....done... 线程: {current_thread().name}')
if __name__ == '__main__':
lock = Condition()
threads_sub = Thread(target=sub, args=(lock,))
threads_add = Thread(target=add, args=(lock, ))
threads_add2 = Thread(target=add, args=(lock,))
threads_sub.start() # 先跑减
threads_add.start() # 再起2个做加的线程
threads_add2.start()
threads_add.join()
threads_sub.join()
threads_add2.join()
print(f'最终账户余额: {NUM}')
PS: 注意,所有wait挂起的线程,一定要有对应的notify,不然线程会一直处于挂起状态,一般用在生产-消费模型中。同时,条件锁也适用with语句。
用于协调线程间通信,主线程控制其他线程的执行。
看下源码,构造方法是一个条件锁, 初始状态是False, 共有4个方法,说明如下。
1.Event().wait(timeout=None):调用该方法的线程会被阻塞,如果设置了timeout参数,超时后,线程会停止阻塞继续执行;
2.Event().set():将event的标志设置为True,调用wait方法的所有线程将被唤醒并执行;
3.Event().clear():将event的标志设置为False,调用wait方法的所有线程将被阻塞,与set作用相反;
4.Event().is_set():判断event的标志是否为True。
一个例子如下(只做演示,代码是个死循环):
import time
from threading import Thread, Event, current_thread
def demo_await(event):
while True:
time.sleep(0.5)
print(f'我是wait: {current_thread().name}, 我要做wait操作')
event.wait()
def demo_set(event):
while True:
time.sleep(0.5)
if not event.is_set(): # 状态为False, 则设置为Ture
print(f'我是set: {current_thread().name}, 我要做set操作')
event.set()
def demo_clear(event):
while True:
time.sleep(0.5)
if event.is_set(): # 状态为Ture, 则设置为False
print(f'我是clear: {current_thread().name}, 我要做clear操作')
event.clear()
if __name__ == '__main__':
event = Event()
# 启动三个线程, 一个线程做await, 一个线程做set, 一个线程做clear
task_await = Thread(target=demo_await, args=(event, ))
task_set = Thread(target=demo_set, args=(event,))
task_clear = Thread(target=demo_clear, args=(event,))
task_await.start()
task_set.start()
task_clear.start()
task_await.join()
task_set.join()
task_clear.join()
import time
from threading import Thread, Semaphore, current_thread
def demo(sem):
sem.acquire()
for i in range(8):
print(f'\n当前线程: {current_thread().name}')
time.sleep(0.5)
sem.release()
if __name__ == '__main__':
sem = Semaphore(3) # 同时只允许三个线程运行
for i in range(10):
Thread(target=demo, args=(sem, )).start()
从源码来看,Semaphore其实是一个条件锁,使用Condition的wait和notify来控制线程的启动和等待。
有以下三个队列:
FIFO ,先进先出队列
LIFO ,后进先出队列(栈)
PRIORTY ,构造一个优先队列
基本使用:
(1)queue.Queue(maxsize=0) FIFO, 如果maxsize小于1就表示队列长度无限
import queue
from threading import Thread, current_thread
def add(que):
for i in range(8):
que.put(i) # 写入队列
print(f'\nadd当前线程: {current_thread().name}')
print(f'add写入队列完成, 当前队列大小: {que.qsize()}')
def sub(que):
for _ in range(8):
if not que.empty():
value = que.get() # 读取队列
print(f'sub从队列取出{value}完成, 当前队列大小: {que.qsize()}')
print(f'\nsub当前线程: {current_thread().name}')
if __name__ == '__main__':
que = queue.Queue() # LIFO, 后进先出队列
Thread(target=add, args=(que,)).start()
Thread(target=sub, args=(que,)).start()
(2)Queue.LifoQueue(maxsize=0) LIFO, 如果maxsize小于1就表示队列长度无限
import queue
from threading import Thread, current_thread
def add(que):
for i in range(8):
que.put(i) # 写入队列
print(f'\nadd当前线程: {current_thread().name}')
print(f'add写入队列完成, 当前队列大小: {que.qsize()}')
def sub(que):
for i in range(8):
if not que.empty():
value = que.get() # 读取队列
print(f'sub从队列取出{value}完成, 当前队列大小: {que.qsize()}')
print(f'\nsub当前线程: {current_thread().name}')
if __name__ == '__main__':
que = queue.LifoQueue() # LIFO, 后进先出队列
Thread(target=add, args=(que,)).start()
Thread(target=sub, args=(que,)).start()
(3)PRIORTY ,继承自Queue, 写入队列时有两个值, 第一个是数字, 第二个是写入队例的值, 读取时按第一个值大小读取,数字越大, 优先级越高。
import queue
from threading import Thread, current_thread
def add(que):
for i in range(8):
que.put(i, i) # 写入队列, 设置数值的优先级, 数字越小, 优先级起大
print(f'\nadd当前线程: {current_thread().name}')
print(f'add写入队列完成, 当前队列大小: {que.qsize()}')
def sub(que):
for _ in range(8):
if not que.empty():
value = que.get() # 读取队列, 按优先级读取
print(f'sub从队列取出{value}完成, 当前队列大小: {que.qsize()}')
print(f'\nsub当前线程: {current_thread().name}')
if __name__ == '__main__':
que = queue.PriorityQueue() # 优先级队例, 后进先出队列
Thread(target=add, args=(que,)).start()
Thread(target=sub, args=(que,)).start()
(4)常用方法如下,比较简单,不做演示了。
Queue.qsize() 返回队列的大小
Queue.empty() 如果队列为空,返回True,反之False
Queue.full() 如果队列满了,返回True,反之False
Queue.get([block[, timeout]]) 读队列,timeout等待时间
Queue.put(item, [block[, timeout]]) 写队列,timeout等待时间
Queue.queue.clear() 清空队列