📜  检查线程是否已在Python中启动

📅  最后修改于: 2022-05-13 01:55:01.923000             🧑  作者: Mango

检查线程是否已在Python中启动

问题:要知道启动的线程何时真正开始运行。

线程的一个关键特性是它们独立且不确定地执行。如果程序中的其他线程在执行进一步操作之前需要知道线程是否已到达其执行中的某个点,这可能会出现一个棘手的同步问题。要解决此类问题,请使用线程库中的 Event 对象。

事件实例类似于允许线程等待某事发生的“粘性”标志。最初,一个事件被设置为 0。如果该事件未设置并且一个线程等待该事件,它将阻塞(即进入睡眠)直到该事件被设置。设置事件的线程将唤醒所有恰好正在等待的线程(如果有的话)。如果线程等待已经设置的事件,它只是继续前进,继续执行。

代码#1:使用事件来协调线程启动的代码。

from threading import Thread, Event
import time
  
# Code to execute in an independent thread
def countdown(n, started_evt):
    print('countdown starting')
    started_evt.set()
  
    while n > 0:
        print('T-minus', n)
        n -= 1
        time.sleep(5)
  
# Create the event object that 
# will be used to signal startup
started_evt = Event()
  
# Launch the thread and pass the startup event
print('Launching countdown')
t = Thread(target = countdown, args =(10, started_evt))
t.start()
  
# Wait for the thread to start
started_evt.wait()
print('countdown is running')

在运行上面的代码时,“倒计时正在运行”消息将始终出现在“倒计时开始”消息之后。这是由使主线程等待直到countdown()函数首次打印启动消息的事件协调的。

  • 事件对象最适合用于一次性事件。即创建一个事件,线程等待该事件被设置,一旦设置,该事件就被丢弃。
  • 虽然可以使用clear()方法清除事件,但安全地清除事件并等待再次设置它是难以协调的,并且可能导致错过事件、死锁或其他问题(特别是,它可以'不能保证在设置事件后清除事件的请求将在释放的线程循环返回以再次等待事件之前执行)。
  • 如果一个线程要一遍又一遍地重复发送一个事件信号,那么最好使用 Condition 对象。

代码#2:实现一个周期性定时器,其他线程可以监视它以查看定时器何时到期。

import threading
import time
  
class PeriodicTimer:
    def __init__(self, interval):
        self._interval = interval
        self._flag = 0
        self._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 ^= 1
            self._cv.notify_all()
              
def wait_for_tick(self):
    '''
    Wait for the next tick of the timer
    '''
    with self._cv:
        last_flag = self._flag
        while last_flag == self._flag:
            self._cv.wait()


代码#3:定时器的使用

# Example use of the timer
ptimer = PeriodicTimer(5)
ptimer.start()
  
# Two threads that synchronize on the timer
def countdown(nticks):
    while nticks > 0:
        ptimer.wait_for_tick()
        print('T-minus', nticks)
        nticks -= 1
          
def countup(last):
    n = 0
    while 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()

Event 对象的一个关键特性是它们唤醒所有等待的线程。如果编写一个只希望唤醒单个等待线程的程序,那么使用 Semaphore 或 Condition 对象可能会更好。代码 #4:涉及信号量的代码

def worker(n, sema):
    # Wait to be signaled
    sema.acquire()
    # Do some work
    print('Working', n)
  
# Create some threads
sema = threading.Semaphore(0)
nworkers = 10
  
for n in range(nworkers):
    t = threading.Thread(target = worker, args =(n, sema, ))
    t.start()

运行此代码时,将启动一个线程池,但没有任何反应,因为它们都被阻塞等待获取信号量。每次释放信号量时,只有一个worker会唤醒并运行如下代码所示代码#5:

sema.release()
sema.release()

输出 :

Working 0
Working 1

编写涉及线程之间大量棘手同步的代码可能会让您头晕目眩。一种更明智的方法是将线程线程化为使用队列或参与者的通信任务。