十二、线程的同步

线程的同步

线程协同工作,通过某些技术保证当一个线程访问某个数据时,其他线程不能访问该数据,直到前面的线程访问结束

事件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())
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值