无法使用Ctrl-C终止Python脚本

140

我正在使用以下脚本测试Python线程:

import threading

class FirstThread (threading.Thread):
    def run (self):
        while True:
            print 'first'

class SecondThread (threading.Thread):
    def run (self):
        while True:
            print 'second'

FirstThread().start()
SecondThread().start()

这是在Kubuntu 11.10上运行Python 2.7。 Ctrl+C无法终止它。我还尝试添加一个处理系统信号的处理程序,但没有帮助:

import signal 
import sys
def signal_handler(signal, frame):
    sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)

我使用进程ID杀死该进程,并在使用Ctrl+Z将程序发送到后台后执行。但是Ctrl+C一直被忽略,为什么?我该如何解决这个问题?


@dotancohen 它在Windows上工作吗? - kiriloff
@vitaibian:我还没有在Windows上测试过,但它似乎不依赖于操作系统。 - dotancohen
5个回答

224

Ctrl+C 终止了主线程,但是因为你的线程没有处于守护模式下,所以它们会继续运行,这将使进程保持活动状态。我们可以将它们设置为守护线程:

f = FirstThread()
f.daemon = True
f.start()
s = SecondThread()
s.daemon = True
s.start()

但是还有另一个问题-一旦主线程启动了你的线程,它就没有其他任务可做。所以它退出并且线程会立即销毁。因此让我们保持主线程存活:

import time
while True:
    time.sleep(1)

现在它将一直打印“first”和“second”,直到你按下Ctrl+C

编辑:正如评论者所指出的,守护线程可能没有机会清理临时文件之类的东西。如果你需要这个,那么在主线程上捕获KeyboardInterrupt并让它协调清理和关闭。但在许多情况下,让守护线程突然死亡可能已经足够了。


9
您应该提到,通过这样做,线程无法正常停止,并且一些资源无法释放。 - Tommaso Barbugli
3
好的,Ctrl-C从来都不是一个优雅的停止方法。我不确定会留下哪些资源——当进程退出时,操作系统不应该回收所有资源吗? - Thomas K
8
tempfile.TemporaryFile() 创建的临时文件可能会留存在磁盘上。 - Feuermurmel
1
@deed02392:我不确定主线程会发生什么,但据我所知,在它退出后你无法对其进行任何操作。进程将在所有非守护线程完成时结束;父子关系与此无关。 - Thomas K
8
在Python3中似乎可以在Thread.__init__中传递daemon=True - Ryan Haining
显示剩余4条评论

8

我认为最好在你希望线程死亡时调用join()。我已经自作主张地更改了你的循环以结束(你也可以添加任何所需的清理)。变量die在每次传递时都会被检查,当它为True时,程序退出。

import threading
import time

class MyThread (threading.Thread):
    die = False
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.name = name

    def run (self):
        while not self.die:
            time.sleep(1)
            print (self.name)

    def join(self):
        self.die = True
        super().join()

if __name__ == '__main__':
    f = MyThread('first')
    f.start()
    s = MyThread('second')
    s.start()
    try:
        while True:
            time.sleep(2)
    except KeyboardInterrupt:
        f.join()
        s.join()

“while True”很傻,你应该直接使用“join”,而且那个被覆盖的函数有点可疑。也许可以这样重写:“def join(self, force=False): if force: self.die = True”,这样“join()”不会受到“join(force=True)”的影响。但即便如此,在加入任何一个线程之前最好先通知两个线程。 - o11c

8

2

@Thomas K的答案的改进版本:

  • 根据此代码片段定义助手函数is_any_thread_alive(),可以自动终止main()

示例代码:

import threading

def job1():
    ...

def job2():
    ...

def is_any_thread_alive(threads):
    return True in [t.is_alive() for t in threads]

if __name__ == "__main__":
    ...
    t1 = threading.Thread(target=job1,daemon=True)
    t2 = threading.Thread(target=job2,daemon=True)
    t1.start()
    t2.start()

    while is_any_thread_alive([t1,t2]):
        time.sleep(0)

0

有一个简单的“陷阱”需要注意,你确定 CAPS LOCK 没有开启吗?

我在 Pi4 上的 Thonny IDE 中运行 Python 脚本。如果 CAPS LOCK 开启,Ctrl+Shift+C 会被传递到键盘缓冲区,而不是 Ctrl+C


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接