网络编程九:线程间的同步之barrier栅栏
总结:
event:用于线程之间的事件通知。
比如,一个线程调用wait方法阻塞,等待事件的发生,另一个线程调用set方法通知事件已发生;wait线程,收到事件发生的通知后,释放阻塞。
可见,多个线程之间必须使用相同的event事件对象。
lock、rlock:用于对共享资源的读写保护。
所有同步方式,都是基于锁实现的。
线程之间的锁lock对象,可以有多个,可以使用不同的锁对象。
只要是加了锁的共享资源,就是受到读写保护的,其它线程要读写此共享资源,必须使用相同的锁解锁以后,才可以读写它;当然,只有相同的锁对象,才可以解开相同的锁。
condition:用于通知等待模式。
比如生产者消费者模式,消费者通过wait之前的acquire阻塞等待,通过wait方法接收通知,
生产者通过notify或nofiy_all方法通知消费者,wait方法收到通知,释放锁。
因此,它是广播的模式。
无论是wait方法,还是notify,notify_all方法,在使用前都必须调用加锁acquire()的方法,使用后调用release()解锁;
通常是在这三个方法之前,使用 with condition语法,来加锁和解锁。
一、Barrier:栅栏
栅栏,用来阻挡线程。设置了一个线程数量障碍,即栅栏强度,为一个数值,当被阻挡的线程达到栅栏强度时,栅栏被冲破,所有被栅栏阻挡的线程被唤醒。
__init__(self, parties, action=None, timeout=None)
parties障碍要求的线程数量
action设置了的话,会在突破障碍的时候被某一个被唤醒的线程调用
timeout给之后的wait()设置了个默认的等待时间
barrier.n_waiting:完成了自己任务的线程,处于等待状态的总数量,即处于等待栅栏的线程数量 。
wait(self, timeout=None) 让处理完自己任务的当前线程进入阻塞状态。即,等待凑齐一波。
abort(self) 强行突破阻碍[wait()等待方法],所有正在等待的线程和要调用wait()方法的线程收到一个BrokenBarrierError异常。通知已经在等待或还未等待的线程,本线程已放弃,大家都不必再等了,抛出异常结束wait()方法。
reset(self) 重置当前对象,所有正在等待的线程收到一个BrokenBarrierError异常
二、满足栅栏条件的示例:
import time import threading import logging import importlib importlib.reload(logging) logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s [%(threadName)s %(message)s]") def worker(barrier: threading.Barrier): # 各线程doing something,n_waiting:现在有多少线程在等待 logging.info("waiting for {} threads...".format(barrier.n_waiting)) try: worker_id = barrier.wait() # 只有满足栅栏条件后,才会执行wait()以后的语句 logging.info("after barrier {}".format(worker_id)) except threading.BrokenBarrierError: logging.warning("abort") NUM_THREADS = 3 barrier = threading.Barrier(NUM_THREADS) for x in range(NUM_THREADS): threading.Thread(target=worker, name='worker-{}'.format(x), args=(barrier,)).start() time.sleep(0.5) logging.info("started!")
执行结果:
2018-12-11 21:17:31,127 INFO [MainThread started!] 2018-12-11 21:17:33,099 INFO [worker-0 waiting for 0 threads...] 2018-12-11 21:17:33,119 INFO [worker-1 waiting for 1 threads...] 2018-12-11 21:17:33,140 INFO [worker-2 waiting for 1 threads...] 2018-12-11 21:17:33,162 INFO [worker-2 after barrier 2] 2018-12-11 21:17:33,162 INFO [worker-1 after barrier 1] 2018-12-11 21:17:33,173 INFO [worker-0 after barrier 0]
threading...start():启动三个栅栏线程---->执行到worker函数时,logging.info...是各线程要做的事情,耗时事件--->执行主线程logging...--->耗时任务结束,进入wait()等待 ---> 一直等到栅栏条件满足时,即定义的3个栅栏对象都满足条件后,wait()语句通过--->栅栏条件wait()通过后,执行wait()之后的语句。
关键点:凑齐一波(即:直到栅栏对象的wait()方法的条件满足),才继续往下执行
满足栅栏条件的场景:多并发,设定一个总线程任务100%,当任务达到60%时,(凑齐一波,继续往下走)其它任务不管了,就算完成。
三、放弃栅栏的示例
abort示例:
NUM_THREADS = 3 barrier = threading.Barrier(NUM_THREADS) for x in range(2): threading.Thread(target=worker, name='worker-{}'.format(x), args=(barrier,)).start() # out:2018-12-11 21:26:39,193 INFO [worker-0 waiting for 0 threads...] # out:2018-12-11 21:26:39,214 INFO [worker-1 waiting for 1 threads...] # barrier对象,要满足3个线程都通过,才算凑齐一波 # 以上,启动两个线程,不满足栅栏条件,处于等待状态
当未满足wait()条件之前,任何一个栅栏线程放弃:都将通过其它完成任务正在等待,或未完成任务的栅栏线程,我放弃了,大家都放弃吧,强制突破wait()等待,抛出threading.BrokenBarrierError异常。
barrier.abort() # out: 2018-12-11 21:31:51,394 WARNING [worker-1 abort] # out: 2018-12-11 21:31:51,412 WARNING [worker-0 abort]
关键点:当barrier.about()方法被执行的时侯,wait()方法会抛出threading.BrokenBarrierError异常,进入异常语句。
放弃栅栏的场景:初始化某系统,有10个工作,必须10个工作都完成,才能初始化完成。任务一个工作未完成,初始化不完成。(必须是无序的10个工作才可以,因为线程是无序的)
四、wait(timeout)
如果因为超时,未凑齐一波,抛出threading.BrokenBarrierError异常,进入异常语句。
在python,其它的timeout都不会抛出异常;栅栏的timeout,会抛出异常,走异常语句。
在业务代码设计的时侯,要注意:
当线程之间需要同步的时侯,才会用到线程之间的通讯,比如锁、事件、通知、信号、栅栏等等;在同步的时间点,线程是阻塞的。
如果线程之间不需要同步,不需要用到线程之间的通讯;将全程并发执行。
posted on 2018-11-04 16:54 myworldworld 阅读(557) 评论(0) 收藏 举报