python 多线程、线程锁、事件
1. 多线程的基本使用
import threading
import time
def run(num):
print('Num: %s'% num)
time.sleep(3)
if num == 4:
print('Thread is finished.')
# 对函数 run 创建5个线程
for i in range(5):
# 创建线程,target:目标函数,args:函数的参数,使用元组的形式
t = threading.Thread(target=run,args=(i,))
# 开始线程活动
t.start()
time.sleep(0.01)
print('Main thread is finished.')
结果:
Num: 0
Num: 1
Num: 2
Num: 3
Num: 4
Main thread is finished.
Thread is finished.
打印的最后两句话顺序是先打印 Main thread is finished. 因为这时主线程已经完成了,而子线程里 slee(3),所以此时子线程尚未完成,大约3秒后,才打印的 Thread is finished.
PS. 线程类的使用
import threading
import time
num = 0
lock = threading.Lock() # 互斥锁,因为多个线程要对共享的数据:num 进行修改
class MyThread(threading.Thread): # 继承线程类
def __init__(self):
super().__init__() # 写了__init__,就一定要继承父类的构造方法。
def task(self): # 自定义的函数
global num
if lock.acquire(): # 如果上锁成功,则执行
num+=1
print(f'{self.name}, Num is : {num}') # 打印线程名和num的值
lock.release() # 释放锁,让数据供其他线程使用
time.sleep(1)
def task2(self):
time.sleep(0.1)
print(f'{self.name}','Task2 is being called.')
time.sleep(1)
def run(self): # 重写父类的run方法,来执行线程任务
self.task()
self.task2()
for i in range(10):
# 针对每个线程对象,start()最多只能调用一次,否则会抛出错误。所以才每循环一次,在循环内部重新生成一个对象
T = MyThread()
# object.start() 会自动调用对象的 run() 方法。
T.start()
2. 等待子线程执行:join
join 的作用是等待子线程执行完毕,才会继续执行后续代码。如果你希望在某块代码之前执行完毕子线程,则可以对子线程使用 join。
当然 join 不是必须的操作,因为默认情况下,哪怕主线程已经执行完毕,它也会等待所有子线程都执行完毕,才会退出程序。
import threading
import time
def run(num):
print('Num: %s'% num)
time.sleep(3)
if num == 4:
print('Thread is finished.')
Tlist = []
for i in range(5):
# 创建线程,target:目标函数,args:函数的参数,使用元组的形式
t = threading.Thread(target=run,args=(i,))
# 开始线程活动
t.start()
Tlist.append(t)
for t in Tlist: # 针对每个子线程,等待子线程执行一段时间,再继续往下执行主线程。
# t.join(0.1) # 针对每个子线程,都只等待 0.1 秒
t.join() # 没写时间,则默认等待子线程执行完毕,才继续往下执行主线程。
print("All thread is finished.")
time.sleep(0.01)
print('Main thread is finished.')
结果:
Num: 0
Num: 1
Num: 2
Num: 3
Num: 4
Thread is finished.
Main thread is finished.
子线程使用了join方法,等待子线程执行完毕才继续执行主线程,所以打印顺序是正常的。
3. 守护线程
如果一个线程是守护线程,那么当主线程执行完毕时,不管子线程状态如何,都会将子线程强制杀掉。
所以如果你将某个子线程设置成了守护线程,并且希望它能完整的执行结束,这时你需要使用 join
来等待该子线程执行完毕,否则一旦主线程结束,子线程也会被强制杀掉(哪怕子线程没有执行完毕)
(默认情况下所有线程都不是守护线程,因此当主线程执行完毕时,会等待所有子线程都执行完毕后,程序才会彻底退出。)
import threading
import time
def run(num):
print('Num: %s'% num)
time.sleep(3)
if num == 4:
print('Thread is finished.')
Tlist = []
for i in range(5):
t = threading.Thread(target=run,args=(i,))
# 设置为守护线程
t.setDaemon(True) # 新版 Python 使用 't.daemon = True'
t.start()
Tlist.append(t)
print('Main thread is finished.')
# print('Current Thread:', threading.active_count())
print('Current Thread:',threading.activeCount()) # 打印当前活动线程数量,这是主线程最后一个语句,所以不管此时还有多少个活动的守护线程,当此句执行完毕,守护线程都会被杀掉。
结果:
Num: 1
Num: 2
Num: 3
Num: 4
Main thread is finished.
Current Thread: 6
4. 线程锁
GIL锁: Global Interpreter Lock,全局解释器锁。为了解决多线程之间数据完整性和状态同步的问题,在任意时刻只有一个线程在解释器中运行。这是CPython中的问题。
线程安全:在多线程中,共享的数据同一时间只能由一个线程执行。
互斥锁:(threading.Lock) 并不关心当前是哪个线程占有了该锁;如果该锁已经被占有了,那么任何其它尝试获取该锁的线程都会被阻塞,包括已经占有该锁的线程也会被阻塞。
递归锁:threading.RLock。RLock 内部维护着一个 Lock 和一个counter 变量,counter 记录了 acquire 的次数,从而使得资源可以被多次请求。直到一个线程所有的 acquire 都被 release,其他的线程才能获得资源(已经占用该锁的线程,可以多次 acquire,并不会阻塞)
死锁:死锁就是当一个线程已经占用了锁并且尚未释放,但是又有新的获取该锁的请求,亦或着两个线程分别占据着对方想要获取的锁,此时线程进入了阻塞的状态,就是死锁。
部份转自:https://www.jb51.net/article/74426.htm
部份转自:https://www.cnblogs.com/ArsenalfanInECNU/p/10022740.html
e.g.
互斥锁:threading.Lock
import threading
class MyThread(threading.Thread):
def run(self): # 这是重写的父类的run方法
global num
if mutex.acquire(): # 判断能否加锁,acquire()返回的是加锁的状态,加锁成功就是True
num = num+1
msg = self.name+' set num to '+str(num)
print(msg)
# 下面两句代码如果不注释掉,就会进入死锁的状态。因为上一个锁还未释放,再次请求加锁,就会阻塞。
# mutex.acquire()
# mutex.release()
mutex.release() # 释放锁
def test():
for i in range(5):
t = MyThread()
t.start()
if __name__ == '__main__':
num = 0
mutex = threading.Lock()
test()
普通函数形式:
import threading
import time
def run(n):
global num
if mutex.acquire(): # 返回获取锁的结果,加锁成功为True,失败false
num = num+1
msg ='Thread-%s set num to ' %n + str(num)
print(msg)
mutex.release()
time.sleep(2)
num = 0
mutex = threading.Lock()
for i in range(5):
t = threading.Thread(target=run,args=(i+1,))
t.start()
递归锁:threading.RLock
import threading
class MyThread(threading.Thread):
def run(self):
global num
if mutex.acquire():
num = num+1
msg = self.name+' set num to '+str(num)
print(msg)
mutex.acquire() # 虽然再次acquire,但是并不会阻塞
mutex.release()
mutex.release()
def test():
for i in range(5):
t = MyThread()
t.start()
if __name__ == '__main__':
num = 0
mutex = threading.RLock()
test()
5. 信号量 semaphore
信号量用于限制同时运行的线程数量,原理是:当线程 accquire 信号量时,信号量的数值会 -1;当线程 release 信号量时,信号量的数值会 +1;当信号量的数值为 0 时进行 accquire,则会阻塞当前线程,直到其他线程调用 release 。
信号量就好像使用名额,用完一个少一个,还回来一个就多一个,值为零时如果想要访问资源,就得排队等着。
import threading
import time
def run(n):
sema.acquire()
print('Thread: %s' %n)
time.sleep(2)
sema.release()
# 声明信号量,同时允许5个线程执行
sema = threading.BoundedSemaphore(5)
for i in range(20):
t = threading.Thread(target=run,args=(i+1,))
t.start()
6. 事件:Event
事件可以用于多个线程进行简单通讯,事件可以控制一个内部 flag,初始时是 False,通过 set()
方法使其变成 True
,clear()
方法使其变成 False
,flag 为 False
时 wait()
方法会一直阻塞直到 flag 为 True
。
import threading
import time
event = threading.Event()
# event.set() # 设置flag为True
# event.clear() # 设置flag为False
# event.wait() # 一直等待(阻塞),直到有线程使用 set 方法将 flag 变成 True
# event.is_set() # 返回flag的值
def boss():
print('Boss: 今天加班5秒钟')
event.clear() # 清除 flag,使其值变为 false
time.sleep(5)
print("Boss: 时间到了,大家回家吧")
event.set() # 设置 flag,使其值变为 True
def employee():
while True:
if event.is_set(): # 如果 flag 为 True
print('Employee: 回家了!')
break
else:
print('Emplyee: 工作中,请勿打扰...')
event.wait() # 如果 flag 为 False,则使用 wait 方法一直等待,直到有线程将其设置为 True
# 创建两个线程,分别为boss和employee,让它们根据 event 进行简单的通讯
b = threading.Thread(target=boss,)
e = threading.Thread(target=employee,)
b.start()
e.start()
结果:
Boss: 今天加班5秒钟
Emplyee: 工作中,请勿打扰...
Boss: 时间到了,大家回家吧
Employee: 回家了!
7. Condition 线程同步
假设场景:一个消费者,一个生产者,一个篮子。生产者生产的东西放到篮子里,消费者自己去篮子中拿产品。
记住下面的几个方法:
accquire()
: 会获取锁
wait()
:会暂时释放锁,并阻塞在这里,此时其他线程会被调度(一旦其他线程中使用 notify()
,则重新触发调度,会继续执行后续的代码)
notify()
:会通知其他线程中的 wait()
状态的线程
release()
:彻底释放锁
from threading import Thread, Condition
import time
items = []
condition = Condition()
class consumer(Thread):
def __init__(self):
Thread.__init__(self)
def consume(self):
global condition
global items
# 加锁
condition.acquire()
if len(items) == 0:
# wait() 会释放锁,然后等待被其他线程 notify, 被 notify 后,会再次获取锁,执行后续代码
condition.wait()
print("消费者:篮子中有商品了,我要消费")
items.pop()
print(f"消费者:我拿走一个了!还剩 {len(items)}")
condition.notify() # 通知其他 wait 中的线程
condition.release() # 释放
def run(self):
for i in range(0, 20):
time.sleep(2)
self.consume()
class producer(Thread):
def __init__(self):
Thread.__init__(self)
def produce(self):
global condition
global items
# 请求锁
condition.acquire()
if len(items) != 0:
# 释放锁 -> 等待被 notify -> 重新获取锁 -> 执行后续代码
condition.wait()
print("生产者:没有商品了,我要开始生产")
items.append(1)
print(f"生产者:生产了一个!还剩 {len(items)}")
condition.notify() # 通知其他 wait 的线程
condition.release() # 释放
def run(self):
for i in range(0, 20):
time.sleep(1)
self.produce()
if __name__ == "__main__":
producer = producer()
consumer = consumer()
producer.start()
consumer.start()
producer.join()
consumer.join()