Python学习线程间同步之一

1、Event

# Event事件,是线程间通信机制中最简单的实现,使用一个内部的标记flag,通过flag的True或False的变化进行操作;

# 总结:使用同一个Event对象的标记flag,默认flag=False;谁wait就是等到flag变为True,或等到超时返回False,不限制等待的个数;

##  模拟实现老板雇佣一个工人生产5个杯子,老板一直等待工人的工作完成 ######
import
threading import logging,time from threading import Event logging.basicConfig(level=10,format="%(asctime)s %(threadName)s %(thread)d %(message)s",datefmt="%Y/%m/%d-%H:%M:%S") def work(event:Event,n): logging.info("I'm working") cups = [] while True: logging.info('make 1') time.sleep(0.5) cups.append(1) if len(cups) >= n: event.set() # 完成任务后将event对象设置为True break logging.info("I' finished my job, cups={}".format(cups)) def boss(event:Event): logging.info("I'M boss waiting for you") event.wait() # boss线程阻塞,等待event对象变为True logging.info("GOOD job")

e=Event() b
=threading.Thread(target=boss,args=(e,),name='boss') w=threading.Thread(target=work,args=(e,5),name='worker') b.start() w.start() ''' 运行结果: 2018/10/30-21:35:23 boss 14388 I'M boss waiting for you 2018/10/30-21:35:23 worker 15244 I'm working 2018/10/30-21:35:23 worker 15244 make 1 2018/10/30-21:35:23 worker 15244 make 1 2018/10/30-21:35:24 worker 15244 make 1 2018/10/30-21:35:24 worker 15244 make 1 2018/10/30-21:35:25 worker 15244 make 1 2018/10/30-21:35:25 worker 15244 I' finished my job, cups=[1, 1, 1, 1, 1] 2018/10/30-21:35:25 boss 14388 GOOD job '''
from threading import Thread,Event
import logging

logging.basicConfig(level=10,format="%(asctime)s %(threadName)s %(thread)d %(message)s",
                    datefmt="%Y/%m/%d-%H:%M:%S")

def do(e:Event,interval:int):
    while not e.wait(interval):
        logging.info('do smthing')

e=Event()
Thread(target=do,args=(e,3),name='do-thread').start()
e.wait(10) #  e.wait() 阻塞当前线程(当前线程为主线程),阻塞操作10秒,如果没有等到flag变为True,则当前线程继续往后执行

# e.set() # 如果e不设置为True,则do线程继续执行
print('MainThread is end')

2、Lock

# 锁,凡是存在资源争抢的地方都可以使用锁,从而保证只有一个使用者可以独享此资源;Lock为互斥锁;

2.1、为什么要加锁

# 由于线程之间的任务执行是cpu进行随机调度的,这就有可能一个线程执行了n条指令之后就被切换到别的线程,开始执行别的线程指令;当个多线程同时操作一个对象,如果没有对该对象上锁,会造成程序的执行结果不可预期,这就为“线程不安全”;

# 为了保证多个线程操作同一个对资源象时候,对象的数据安全与完整,则需要将资源对象家锁;

2.2、加锁与解锁

# Lock 锁,一旦线程获得锁,其他试图获取锁的线程将被阻塞;

# 加锁与解锁的方法:

①:使用try...finally语句保证锁的释放;

②:with上下文管理,锁对象支持上下文管理;

l=Lock()
cups=[]
e=Event()

def boss():
    logging.info("I'm boss,waiting for you")
    e.wait()
    print('good job,cups lens={}'.format(len(cups)))

def worker():
    logging.info("I'm working")
    e.wait(1)
    with l:
        while True:
            if len(cups) >= 100:
                logging.info('Finish JOB')
                e.set()
                break
            else:
                cups.append(1)

b=Thread(target=boss,name='boss')
b.start()

for i in range(10):
    w=Thread(target=worker,name='worker-{}'.format(i))
    w.start()

2.3、非阻塞锁

# 阻塞锁与非阻塞锁取决于在获取锁的时候是blocking=False还是True;

# 什么时候阻塞? 当一个线程成功获取锁之后,无论是阻塞或是非阻塞都不影响该线程的继续执行;但是如果此时另外一个线程也要获取该锁,该线程的阻塞与非阻塞就要看该线程获取锁的方式了;

def test():
    print('2,----')
    l.acquire()
    print('3,~~~~~~~~~')

l=Lock()
l.acquire()
print('1,=======')
Thread(target=test).start()

print('===MainThreadEnd====')

'''
主线程获得锁l.acquire()对象,主线程继续执行,打印1,=======;
继续执行到Thread的时候会启动一个工作线程,执行test函数;
test函数内执行到l.acquire()的时候,由于主线程并没有释放锁,并且工作线程是l.acquire() 阻塞的方式获取锁,所以工作线程被阻塞;
如果以l.acquire(False) 非阻塞的方式获取锁,则工作线程继续往下执行打印print('3,~~~~~~~~~')
'''
def worker(tasks):
    for task in tasks:
        time.sleep(0.001)
        if task.lock.acquire(False):
        # 获取锁则返回True, 此时如果acquire(True) 阻塞的方式,则只有1个工作线程工作,其他的5个线程都被阻塞,因为没有释放锁;
            logging.info('{} {} begin to start'.format(threading.current_thread(),task.name))
        else:
            logging.info('{} {} is working'.format(threading.current_thread(),task.name))

class Task:
    def __init__(self,name):
        self.name=name
        self.lock=Lock()

tasks=[Task('task-{}'.format(x)) for x in range(5)] # 构造5个任务,每个任务对象都有自己的一把锁
# 启动5个线程
for i in range(5):

    Thread(target=worker,args=(tasks,),name='worker-{}'.format(i)).start()

'''
阻塞锁acquire()运行结果:
    2018/11/01-15:33:37 worker-0 10932 <Thread(worker-0, started 10932)> task-0 begin to start
    2018/11/01-15:33:37 worker-0 10932 <Thread(worker-0, started 10932)> task-1 begin to start
    2018/11/01-15:33:37 worker-0 10932 <Thread(worker-0, started 10932)> task-2 begin to start
    2018/11/01-15:33:37 worker-0 10932 <Thread(worker-0, started 10932)> task-3 begin to start
    2018/11/01-15:33:37 worker-0 10932 <Thread(worker-0, started 10932)> task-4 begin to start
非阻塞锁acquire(False)运行结果:
    2018/11/01-15:34:31 worker-2 1704 <Thread(worker-2, started 1704)> task-0 begin to start
    2018/11/01-15:34:31 worker-4 10856 <Thread(worker-4, started 10856)> task-0 is working
    2018/11/01-15:34:31 worker-3 2608 <Thread(worker-3, started 2608)> task-0 is working
    2018/11/01-15:34:31 worker-1 5560 <Thread(worker-1, started 5560)> task-0 is working
    2018/11/01-15:34:31 worker-0 9468 <Thread(worker-0, started 9468)> task-0 is working
    2018/11/01-15:34:31 worker-4 10856 <Thread(worker-4, started 10856)> task-1 begin to start
    2018/11/01-15:34:31 worker-2 1704 <Thread(worker-2, started 1704)> task-1 is working
    2018/11/01-15:34:31 worker-3 2608 <Thread(worker-3, started 2608)> task-1 is working
    2018/11/01-15:34:31 worker-0 9468 <Thread(worker-0, started 9468)> task-1 is working
    2018/11/01-15:34:31 worker-1 5560 <Thread(worker-1, started 5560)> task-1 is working
    2018/11/01-15:34:31 worker-4 10856 <Thread(worker-4, started 10856)> task-2 begin to start
    ....
'''

3、RLock

# RLock,可重入锁,是线程相关的锁;

# RLock本质上与Lock类似,也有阻塞和非阻塞的模式,但是RLock锁对象内部维护一个计数器count,在一个线程内可以多次成功获取,acquire()一次就count+=1;release()一次count-=1,release次数不能大于acquire次数

# 其他线程要成功获取该锁,则需要该锁对象内部计数器count=0;

4、Condition

# 构造方法Condition(Lock=None),可以传入一个Lock或者RLock对象,默认是RLock;

# Condition用于生产者消费者模型中,解决了生产者消费者速度匹配的问题;采用了通知机制,非常有效率;

# 使用方式:

  使用Condition,必须先acquire,用完了要release,因为内部使用了锁,默认使用RLock,最好的方式是使用with进行上下文管理(自动加锁和解锁);

  消费者wait,等待通知;

  生产者生产完成,对消费者发送通知,可以使用notify或者notify_all 方法;

# 生产者消费者模型代码实现

class Dispather:
    def __init__(self):
        self.data=None
        self.event=Event()
        self.cond=Condition()
    def produce(self,total):
        for _ in range(total):
            data=random.randint(0,100)
            with self.cond:
                logging.info(data)
                self.data=data
                self.cond.notify_all()
            self.event.wait(1) # 模拟生产数据速度
        self.event.set() # 数据生产完成将event设置为True

    def consume(self):
        while not self.event.is_set(): # 默认event.is_set()为False,循环不进入,等待event.set后开始进入循环;
            with self.cond:
                self.cond.wait() # 阻塞等待通知
                logging.info('received {}'.format(self.data))

            self.event.wait(0.5) # 模拟消费速度

d=Dispather()
p=Thread(target=d.produce,args=(10,),name='producer')

for i in range(2):

    c= Thread(target=d.consume,name='consumer-{}'.format(i))
    c.start()

p.start()

 

posted @ 2018-10-30 21:38  soulgou  阅读(518)  评论(0编辑  收藏  举报