网络编程六:线程间的同步之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)    收藏  举报

导航