Python 线程在 Python 中使用 ctrl-c 退出

Python threads exit with ctrl-c in Python

我有如下的 Python 多线程程序。如果我在 5 秒内(大约)按下 ctrl+c,它将进入 KeyboardInterrupt 异常.

运行 超过 15 秒的代码未能响应 ctrl+c。 如果我在 15 秒后按 ctrl+c,它不起作用。 没有抛出 KeyboardInterrupt 异常。可能是什么原因 ?我在 Linux.

上测试了这个
#!/usr/bin/python

import os, sys, threading, time

class Worker(threading.Thread):
  def __init__(self):
    threading.Thread.__init__(self)
    # A flag to notify the thread that it should finish up and exit
    self.kill_received = False

  def run(self):
      while not self.kill_received:
          self.do_something()

  def do_something(self):
      [i*i for i in range(10000)]
      time.sleep(1)

def main(args):

    threads = []
    for i in range(10):
        t = Worker()
        threads.append(t)
        t.start()

    while len(threads) > 0:
        try:
            # Join all threads using a timeout so it doesn't block
            # Filter out threads which have been joined or are None
            threads = [t.join(1) for t in threads if t is not None and t.isAlive()]
        except KeyboardInterrupt:
            print "Ctrl-c received! Sending kill to threads..."
            for t in threads:
                t.kill_received = True

if __name__ == '__main__':
  main(sys.argv)

第一次执行后

threads = [t.join(1) for t in threads if t is not None and t.isAlive()]

你的变量threads包含

[None, None, None, None, None, None, None, None, None, None]

第二次执行后,同一个变量threads包含:

[]

此时,len(threads) > 0 为 False,您就退出了 while 循环。你的脚本仍然是 运行 因为你有 10 个线程仍然处于活动状态,但是因为你不再在你的 try / except 块中(捕捉 KeyboardInterrupt),你不能停止使用 Ctrl + C

在您的脚本中添加一些打印件以查看我所描述的内容:

#!/usr/bin/python

import os, sys, threading, time

class Worker(threading.Thread):
  def __init__(self):
    threading.Thread.__init__(self)
    # A flag to notify the thread that it should finish up and exit
    self.kill_received = False

  def run(self):
      while not self.kill_received:
          self.do_something()

  def do_something(self):
      [i*i for i in range(10000)]
      time.sleep(1)

def main(args):

    threads = []
    for i in range(10):
        t = Worker()
        threads.append(t)
        t.start()
        print('thread {} started'.format(i))

    while len(threads) > 0:
        print('Before joining')
        try:
            # Join all threads using a timeout so it doesn't block
            # Filter out threads which have been joined or are None
            threads = [t.join(1) for t in threads if t is not None and t.isAlive()]
            print('After join() on threads: threads={}'.format(threads))

        except KeyboardInterrupt:
            print("Ctrl-c received! Sending kill to threads...")
            for t in threads:
                t.kill_received = True
    print('main() execution is now finished...')

if __name__ == '__main__':
  main(sys.argv)

结果:

$ python thread_test.py
thread 0 started
thread 1 started
thread 2 started
thread 3 started
thread 4 started
thread 5 started
thread 6 started
thread 7 started
thread 8 started
thread 9 started
Before joining
After join() on threads: threads=[None, None, None, None, None, None, None, None, None, None]
Before joining
After join() on threads: threads=[]
main() execution is now finished...

其实Ctrl+C并不是15秒后停止工作,而是10秒或11秒后停止工作。这是创建和启动 10 个线程(不到一秒)以及在每个线程上执行 join(1)(大约 10 秒)所需的时间。

来自 the doc 的提示:

As join() always returns None, you must call isAlive() after join() to decide whether a timeout happened – if the thread is still alive, the join() call timed out.

为了跟进上面的海报,isAlive() 已重命名为 is_alive() 试用了 Python 3.9.6

完整代码:

#!/usr/bin/python

import os, sys, threading, time

class Worker(threading.Thread):
  def __init__(self):
    threading.Thread.__init__(self)
    # A flag to notify the thread that it should finish up and exit
    self.kill_received = False

  def run(self):
      while not self.kill_received:
          self.do_something()

  def do_something(self):
      [i*i for i in range(10000)]
      time.sleep(1)

def main(args):

    threads = []
    for i in range(10):
        t = Worker()
        threads.append(t)
        t.start()
        print('thread {} started'.format(i))

    while len(threads) > 0:
        print('Before joining')
        try:
            # Join all threads using a timeout so it doesn't block
            # Filter out threads which have been joined or are None
            threads = [t.join(1) for t in threads if t is not None and t.is_alive()]
            print('After join() on threads: threads={}'.format(threads))

        except KeyboardInterrupt:
            print("Ctrl-c received! Sending kill to threads...")
            for t in threads:
                t.kill_received = True
    print('main() execution is now finished...')

if __name__ == '__main__':
  main(sys.argv)