网络编程六:线程间的同步之event事件
5个worker线程,一个boss线程:当worker执行完成后,通知boss线程,将告知它的执行时长。
要实现此功能,需要线程间的同步。
一、同步方式一:事件event
event = threading.Event()
event.set()
event.wait()
wait方法,阻塞线程,直到set方法被调用。clear方法,重置event事件。
import threading, time, random, logging logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s [%(threadName)s] -- %(message)s") def worker(event: threading.Event): sleep_time = random.randint(1, 5) time.sleep(sleep_time) event.set() logging.info("worker sleep {}".format(sleep_time)) def boss(event: threading.Event): start = int(time.time()) event.wait() logging.info("worker exit {}".format(int(time.time()) - start)) def start(): event = threading.Event() b = threading.Thread(target=boss, name="boss", args=(event,)) b.start() for i in range(5): w = threading.Thread(target=worker, name="worker-{}".format(i), args=(event,)) w.start() start()
2018-11-03 16:51:21,469 INFO [worker-0] -- worker sleep 2 # 第一个执行完成的worker线程 2018-11-03 16:51:21,469 INFO [boss] -- worker exit 2 # 一旦有worker线程结束,boss线程退出 2018-11-03 16:51:21,469 INFO [worker-1] -- worker sleep 2 # 其它worker线程继续执行 2018-11-03 16:51:22,469 INFO [worker-2] -- worker sleep 3 2018-11-03 16:51:22,469 INFO [worker-4] -- worker sleep 3 2018-11-03 16:51:24,469 INFO [worker-3] -- worker sleep 5
假设现在的需求是:有任何一个worker线程退出时,其它的worker线程也退出。
思路:
wait()方法,除了可以由set方法释放之外,也可以由它的timeout参数,超时后释放。
除了让boss线程阻塞,等待set()方法调用时,释放阻塞。
所有的worker线程也调用wait方法阻塞,第一个执行完的worker线程由wait()方法的超时释放;
但是它只会释放当前线程,其它work线程和boss线程不会释放,还会继承wait,直到超时或set方法被调用;
因此,当第一个worker线程超时释放后,调用set方法,则可让其它的work线程和boss线程释放。
import threading, time, random, logging logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s [%(threadName)s] -- %(message)s") def worker(event: threading.Event): sleep_time = random.randint(1, 5) event.wait(sleep_time) # 让所有的worker线程也阻塞 event.set() # 当第一个worker超时时,调用set()方法:则boss线程和其它worker线程的wait方法被同时释放 logging.info("worker sleep {}".format(sleep_time)) def boss(event: threading.Event): start = int(time.time()) event.wait() logging.info("worker exit {}".format(int(time.time()) - start)) def start(): event = threading.Event() b = threading.Thread(target=boss, name="boss", args=(event,)) b.start() for i in range(5): w = threading.Thread(target=worker, name="worker-{}".format(i), args=(event,)) w.start() start()
2018-11-03 16:56:22,890 INFO [worker-0] -- worker sleep 2 # 第一个worker线程,由于超时,wait被释放;同时调用set()方法,让其它worker线程和boss线程释放 2018-11-03 16:56:22,890 INFO [boss] -- worker exit 2 # 第一个worker线程调用set()方法后,释放boss线程 2018-11-03 16:56:22,890 INFO [worker-1] -- worker sleep 5 # 第一个worker线程调用set()方法后,释放其它worker线程; 其它的worker线程都在同一时间结束 2018-11-03 16:56:22,890 INFO [worker-3] -- worker sleep 3 2018-11-03 16:56:22,890 INFO [worker-2] -- worker sleep 5 2018-11-03 16:56:22,890 INFO [worker-4] -- worker sleep 5
假设:boss统计每个worker的执行时间。
要实现它,boss需要等待所有worker退出后,才退出,为每个worker计算时长。
import threading, time, random, logging logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s [%(threadName)s] -- %(message)s") def worker(event: threading.Event): sleep_time = random.randint(1, 5) time.sleep(sleep_time) logging.info("worker sleep {}".format(sleep_time)) event.set() def boss(num, event: threading.Event): start = int(time.time()) for i in range(num): event.wait() logging.info("worker exit {}".format(int(time.time()) - start)) def start(): event = threading.Event() b = threading.Thread(target=boss, name="boss", args=(5, event)) b.start() for i in range(5): w = threading.Thread(target=worker, name="worker-{}".format(i), args=(event,)) w.start() start()
执行结果:
2018-11-03 17:22:52,969 INFO [worker-2] -- worker sleep 1 2018-11-03 17:22:52,969 INFO [worker-4] -- worker sleep 1 2018-11-03 17:22:52,969 INFO [boss] -- worker exit 1 2018-11-03 17:22:52,969 INFO [boss] -- worker exit 1 2018-11-03 17:22:52,969 INFO [boss] -- worker exit 1 2018-11-03 17:22:52,969 INFO [boss] -- worker exit 1 2018-11-03 17:22:52,969 INFO [boss] -- worker exit 1 2018-11-03 17:22:54,969 INFO [worker-3] -- worker sleep 3 2018-11-03 17:22:55,969 INFO [worker-1] -- worker sleep 4 2018-11-03 17:22:56,969 INFO [worker-0] -- worker sleep 5
从结果,可以看出,虽然每个worker都正常执行完毕,才退出。但boss线程,在第一次被set()之后,再次设置wait(),没有超作用。
问题:event被set之后,再次调用wait方法不起作用,怎么办?
解决办法:使用event.clear()方法,重置event事件。
import threading, time, random, logging logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s [%(threadName)s] -- %(message)s") def worker(event: threading.Event): sleep_time = random.randint(1, 5) time.sleep(sleep_time) logging.info("worker sleep {}".format(sleep_time)) event.set() def boss(num, event: threading.Event): start = int(time.time()) for i in range(num): event.wait() logging.info("worker exit {}".format(int(time.time()) - start)) event.clear() # 必须调用event.clear()后,下次event.wait()才会生效 def start(): event = threading.Event() b = threading.Thread(target=boss, name="boss", args=(5, event)) b.start() for i in range(5): w = threading.Thread(target=worker, name="worker-{}".format(i), args=(event,)) w.start() start()
2018-11-03 17:31:27,217 INFO [worker-2] -- worker sleep 3 2018-11-03 17:31:27,217 INFO [boss] -- worker exit 3 2018-11-03 17:31:28,217 INFO [worker-4] -- worker sleep 4 2018-11-03 17:31:28,217 INFO [worker-1] -- worker sleep 4 2018-11-03 17:31:28,217 INFO [boss] -- worker exit 4 2018-11-03 17:31:29,217 INFO [worker-0] -- worker sleep 5 2018-11-03 17:31:29,217 INFO [worker-3] -- worker sleep 5 2018-11-03 17:31:29,217 INFO [boss] -- worker exit 5
上面的代码,还是有点问题:假如有worker同时退出,boss只会被释放一次wait。导致boss释放的次数,少于wait的次数,程序一直阻塞。
即多次set,也只会释放一次wait。
或者说wait只等待一次,不管set几次。
除非调用clear,重置事件,wait方法才会再次生效。
但是当有event同时set时,wait只会释放一次,clear还没来得及重置event,此时这种场景不适合event够解决。
这种场景,set几次,wait释放几次,event做不到;使用可重复锁lock可以实现,或者队列。
可见,event可以在线程之间发送信号。
通常用于某个线程,需要等待其它线程处理完成某些动作之后,才能启动。
可以有多个线程同时持有wait,只要有一个线程set,所有线程的wait都将释放;当然要保证多个线程中,使用的是同一个event对象。
二、巧用wait(timeout)返回值
- wait由set方法释放时,返回值为True
- wait由超时释放时,返回值为False
import threading event = threading.Event() print(event.wait(1)) # 超时,wait返回False
import threading event = threading.Event() def work(): print (event.wait()) # 由set释放,返回True def boss(): event.set() threading.Thread(target=work).start() threading.Thread(target=boss).start()
利用wait超时,返回False,实现定时循环任务:每3秒执行一次
import threading event = threading.Event() while not event.wait(3): print ("run......")
三、巧用event.is_set()返回值
类似使用wait超时,返回False差不多。
- event.is_set()方法,由set方法释放时,返回值为True
- event.is_set()方法,由clear方法清除set标志时,返回值为False
import threading event = threading.Event() event.clear() print(event.is_set()) # clear方法清除set标志时,is_set()方法返回False
import threading event = threading.Event() event.set() print(event.is_set()) # 由set释放,is_set()方法返回True
利用clear方法清除set标志(同时释放wait),或者未调用set方法时(不释放wait),返回False,实现定时循环任务:每3秒执行一次
import threading, time event = threading.Event() while not event.is_set(): time.sleep(3) print ("run....")
wait与sleep的区别:event.wait阻塞时,会让出cpu时间片;time.sleep时,不会让出cpu时间片。
四、使用event.wait或event.is_set()实现延迟线程threading.Timer
import threading, time class Timer: def __init__(self, interval, function, *args, **kwargs): self.interval = interval self.function = function self.args = args self.kwargs = kwargs self.event = threading.Event() self.thread = threading.Thread(target=self._target) def _target(self): if not self.event.wait(self.interval): # if not self.event.is_set(): self.function(*self.args, **self.kwargs) def start(self): self.thread.start() def cancel(self): self.event.set() def worker(): print ("worker run......") t = Timer(5, function=worker) t.start()
五、wait、set、clear、is_set使用示例:
event=threading.Event() #设置一个事件实例
event.set() #设置标志位
event.clear() #清空标志位
event.wait() #等待设置标志位
'''标志位设定,代表绿灯,直接通行;标志位被清空,代表红灯;wait()等待变绿灯'''
import threading,time event=threading.Event() def lighter(): '''0<count<5为绿灯,5<count<10为红灯,count>10重置标志位''' event.set() count=0 while True: if 10 > count > 5: event.clear() # 清空set标志位,此时event.is_set()返回False print("\033[1;41m red light is on \033[0m") elif count > 10: event.set() # 设置set标志位,此时event.is_set()返回True count=0 else: print("\033[1;42m green light is on \033[0m") time.sleep(1) count+=1 def car(name): '''红灯停,绿灯行''' while True: if event.is_set(): # 当绿灯时,调用set方法,event.is_set()返回True print("[%s] is running..." % name) time.sleep(0.2) else: print("[%s] sees red light,need to wait three seconds" % name) event.wait() print("\033[1;34;40m green light is on,[%s]start going \033[0m" % name) light = threading.Thread(target=lighter) light.start() car1=threading.Thread(target=car, args=("bmw",)) car1.start() # 示例参考:https://blog.csdn.net/beifangdefengchuilai/article/details/80165772
posted on 2018-11-03 18:07 myworldworld 阅读(369) 评论(0) 收藏 举报