12.2 判断线程是否已经启动
最后更新于:2022-04-01 15:39:07
## 问题
You’ve launched a thread, but want to know when it actually starts running.
## 解决方案
A key feature of threads is that they execute independently and nondeterministically.This can present a tricky synchronization problem if other threads in the program needto know if a thread has reached a certain point in its execution before carrying outfurther operations. To solve such problems, use the Event object from the threadinglibrary.Event instances are similar to a “sticky” flag that allows threads to wait for somethingto happen. Initially, an event is set to 0. If the event is unset and a thread waits on theevent, it will block (i.e., go to sleep) until the event gets set. A thread that sets the eventwill wake up all of the threads that happen to be waiting (if any). If a thread waits on anevent that has already been set, it merely moves on, continuing to execute.Here is some sample code that uses an Event to coordinate the startup of a thread:
from threading import Thread, Eventimport time
# Code to execute in an independent threaddef countdown(n, started_evt):
> > print(‘countdown starting')started_evt.set()while n > 0:
> > print(‘T-minus', n)n -= 1time.sleep(5)
# Create the event object that will be used to signal startupstarted_evt = Event()
# Launch the thread and pass the startup eventprint(‘Launching countdown')t = Thread(target=countdown, args=(10,started_evt))t.start()
# Wait for the thread to startstarted_evt.wait()print(‘countdown is running')
When you run this code, the “countdown is running” message will always appear afterthe “countdown starting” message. This is coordinated by the event that makes the mainthread wait until the countdown() function has first printed the startup message.
## 讨论
Event objects are best used for one-time events. That is, you create an event, threadswait for the event to be set, and once set, the Event is discarded. Although it is possibleto clear an event using its clear() method, safely clearing an event and waiting for itto be set again is tricky to coordinate, and can lead to missed events, deadlock, or otherproblems (in particular, you can’t guarantee that a request to clear an event after settingit will execute before a released thread cycles back to wait on the event again).If a thread is going to repeatedly signal an event over and over, you’re probably betteroff using a Condition object instead. For example, this code implements a periodic timerthat other threads can monitor to see whenever the timer expires:
import threadingimport time
class PeriodicTimer:def __init__(self, interval):self._interval = intervalself._flag = 0self._cv = threading.Condition()def start(self):
t = threading.Thread(target=self.run)t.daemon = True
t.start()
def run(self):
‘''Run the timer and notify waiting threads after each interval‘''while True:
> > time.sleep(self._interval)with self._cv:
> > self._flag ^= 1self._cv.notify_all()
def wait_for_tick(self):
‘''Wait for the next tick of the timer‘''with self._cv:
> > last_flag = self._flagwhile last_flag == self._flag:
> > self._cv.wait()
# Example use of the timerptimer = PeriodicTimer(5)ptimer.start()
# Two threads that synchronize on the timerdef countdown(nticks):
> while nticks > 0:ptimer.wait_for_tick()print(‘T-minus', nticks)nticks -= 1
def countup(last):
n = 0while n < last:
> ptimer.wait_for_tick()print(‘Counting', n)n += 1
threading.Thread(target=countdown, args=(10,)).start()threading.Thread(target=countup, args=(5,)).start()
A critical feature of Event objects is that they wake all waiting threads. If you are writinga program where you only want to wake up a single waiting thread, it is probably betterto use a Semaphore or Condition object instead.For example, consider this code involving semaphores:
# Worker threaddef worker(n, sema):
> > # Wait to be signaledsema.acquire()
> # Do some workprint(‘Working', n)
# Create some threadssema = threading.Semaphore(0)nworkers = 10for n in range(nworkers):
> t = threading.Thread(target=worker, args=(n, sema,))t.start()
If you run this, a pool of threads will start, but nothing happens because they’re allblocked waiting to acquire the semaphore. Each time the semaphore is released, onlyone worker will wake up and run. For example:
>>> sema.release()
Working 0
>>> sema.release()
Working 1
>>>
Writing code that involves a lot of tricky synchronization between threads is likely tomake your head explode. A more sane approach is to thread threads as communicatingtasks using queues or as actors. Queues are described in the next recipe. Actors aredescribed in Recipe 12.10.