线程的同步
线程协同工作,通过某些技术保证当一个线程访问某个数据时,其他线程不能访问该数据,直到前面的线程访问结束
事件Event
在进程内部设定一个flag,通过flag的变化来控制代码执行是否等待,这个flag就是事件实例。
事件实例需要threading模块中的类Event来创建。
名字 | 含义 |
---|---|
set() | 设置事件实例为True |
clear() | 设置事件实例为False |
is_set() | 判定事件实例是否为True |
wait(timeout=None) | 设置等待事件实例,None表示无限等待。等到超时则返回False |
代码执行过程中遇到event.wait()就发生阻塞,等待event编程True。
举例说明,以交通灯红绿切换,汽车通行需等待红绿灯,设定一个线程是车子通行,另一个线程是交通灯切换
from threading import Event,Thread
import logging
import time
FORMAT='%(asctime)s %(threadName)s %(thread)d %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)
def car(event):
logging.info('车子在等红灯')
event.wait()
logging.info('车子通过')
def deng(event):
for i in range(5,0,-1):
logging.info('现在是红灯,还有%ds'%i)
time.sleep(1)
event.set()
logging.info('现在是绿灯')
event=Event()
c=Thread(target=car,args=(event,))
d=Thread(target=deng,args=(event,))
c.start()
d.start()
#输出结果如下
2019-07-04 18:45:52,985 Thread-1 123145474813952 车子在等红灯
2019-07-04 18:45:52,985 Thread-2 123145480069120 现在是红灯,还有5s
2019-07-04 18:45:53,987 Thread-2 123145480069120 现在是红灯,还有4s
2019-07-04 18:45:54,991 Thread-2 123145480069120 现在是红灯,还有3s
2019-07-04 18:45:55,993 Thread-2 123145480069120 现在是红灯,还有2s
2019-07-04 18:45:56,994 Thread-2 123145480069120 现在是红灯,还有1s
2019-07-04 18:45:57,999 Thread-2 123145480069120 现在是绿灯
2019-07-04 18:45:57,999 Thread-1 123145474813952 车子通过
from threading import Event,Thread
import logging
import datetime
FORMAT='%(asctime)s %(threadName)s %(thread)d %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)
def add(x,y):
logging.info(x+y)
class Timer:
def __init__(self,interval,fn,*args,**kwargs):
self.interval=interval
self.fn=fn
self.args=args
self.kwargs=kwargs
self.event=Event() ##为一个Timer类的实例创建一个属性event,该属性为事件实例,初始为False。
def start(self):
t=Thread(target=self.__run)
t.start()
def cancel(self):
self.event.set()
def __run(self):
start=datetime.datetime.now()
logging.info('waiting')
self.event.wait(self.interval) ##遇到event.wait发生阻塞,等待Timer类的实例属性event变为True。超时时间为self.interval
if not self.event.is_set():##如果self.event是False,则执行self.fn函数
self.fn(*self.args,**self.kwargs)
delta=(datetime.datetime.now()-start).total_seconds()
logging.info('finshed {}'.format(delta))
self.event.set()
T=Timer(10,add,4,50) #依据类Timer实例化一个对象T
T.start()#调用实例T的start方法生成一个子线程,该子线程运行self__run方法
e=Event()#创建一个事件实例e
e.wait(4)#发生阻塞,等待超时,继续执行后面的代码
T.cancel() #将实例t的event属性修改为Ture
print('end')
#输出结果
2019-07-04 22:43:39,839 Thread-1 123145402576896 waiting
#(此处等待4秒,如果主线程不执行T.cancel()则等待10秒之后输出add函数)
2019-07-04 22:43:43,843 Thread-1 123145402576896 finshed 4.003959
end
例子中实例T是我们创建的Timer类实例,不是线程。
锁Lock
凡是有共享资源竞争的时候,就可以进行加锁控制,从而保证只有一个使用者获取资源。
举例:10个工人生成1000个杯子,10个工人就是10个子线程。杯子是数量是列表cups,10个工人每次生产前需要对列表进行访问和修改。如果没有加锁进行控制,可能导致列表中已经满1000个了,但某些工人获取的列表滞后少于1000个,仍继续生产。
from threading import Lock,Thread
import logging
import time
FORMAT='%(asctime)s %(threadName)s %(thread)d %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)
cups=[]
lock=Lock()
def worker(count):
logging.info('start work...')
flag=False
while True:
lock.acquire()
if len(cups) >=count:
flag=True
if not flag:
time.sleep(0.001)
cups.append(1)
lock.release()
if flag:
break
logging.info('work finshed count={}'.format(len(cups)))
for i in range(10):
Thread(target=worker,args=(100,)).start()
死锁
在加锁和解锁之间有一段代码执行,如果这段代码抛异常那么就无法解锁了,这就导致死锁了。
可重复入锁RLock
RLock内部维护着一个Lock和一个counter变量,RLock锁可以一个线程多次acquire()而不被阻塞,counter记录了acquire的次数。直到一个用于锁的线程将其所有的acquire都release,其他的线程才能获得锁。
应用场景:
主要就是对于你的线程处理中会有一些比较复杂的代码逻辑过程,比如很多层的函数调用,而这些函数其实都需要进行加锁保护数据访问。
这样就可能会反复的多次加锁,因而用rlock就可以进行多次加锁,解锁,直到最终锁被释放
而如果用普通的lock,当你一个函数A已经加锁,它内部调用另一个函数B,如果B内部也会对同一个锁加锁,那么这种情况就也会导致死锁。而rlock可以解决这个问题
condition
from threading import Event,Thread,Condition,active_count,enumerate
import logging
import random
import time
FORMAT='%(asctime)s %(threadName)s %(thread)d %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)
class Dis:
def __init__(self):
self.data=None
self.event=Event()
self.cond=Condition() #2
def pro(self,total):
for i in range(total):
data=random.randint(0,100)
# logging.info(data)
# self.data=data
with self.cond:
logging.info(data)
self.data=data
self.cond.notify_all()
logging.info('pro working event is {}'.format(self.event.is_set()))
self.event.wait(1)
self.event.set()
logging.info('pro finshed event is {}'.format(self.event.is_set()))
def con(self):
while not self.event.is_set():
logging.info('xunhuan')
# logging.info('received {}'.format(self.data))
# self.data=None
with self.cond:
self.cond.wait()
logging.info('received {}'.format(self.data))
self.data=None
self.event.wait(0.5)
logging.info('con received {}'.format(self.event.is_set()))
d=Dis()
Thread(target=d.con,name='con',daemon=True).start()
Thread(target=d.pro,args=(5,),name='pro').start()
print(active_count(),enumerate())