Python 中用 Ctrl+C 终止多线程程序的问题解决

Python 中用 Ctrl+C 终止多线程程序的问题解决


     花了一天时间用python为服务写了个压力测试。很简单,多线程向服务器发请求。但写完之后发现如果中途想停下来,按Ctrl+C达不到效果,自然想到要用信号处理函数捕捉信号,使线程都停下来,问题解决的方法请往下看:

#!/bin/env python
# -*- coding: utf-8 -*-
#filename: peartest.py

import threading, signal

is_exit = False

def doStress(i, cc):
    global is_exit
    idx = i
    while not is_exit:
    if (idx < 10000000):
        print "thread[%d]: idx=%d"%(i, idx)
        idx = idx + cc
    else:
        break
    print "thread[%d] complete."%i

def handler(signum, frame):
    global is_exit
    is_exit = True
    print "receive a signal %d, is_exit = %d"%(signum, is_exit)

if __name__ == "__main__":
    signal.signal(signal.SIGINT, handler)
    signal.signal(signal.SIGTERM, handler)
    cc = 5
    for i in range(cc):
        t = threading.Thread(target=doStress, args=(i,cc))
        t.start()

上面是一个模拟程序,并不真正向服务发送请求,而代之以在一千万以内,每个线程每隔并发数个(cc个)打印一个整数。很明显,当所有线程都完成自己的任务后,进程会正常退出。但如果我们中途想退出(试想一个压力测试程序,在中途已经发现了问题,需要停止测试),该肿么办?你当然可以用ps查找到进程号,然后kill -9杀掉,但这样太繁琐了,捕捉Ctrl+C是最自然的想法。上面示例程序中已经捕捉了这个信号,并修改全局变量is_exit,线程中会检测这个变量,及时退出。

但事实上这个程序并不work,当你按下Ctrl+C时,程序照常运行,并无任何响应。网上搜了一些资料,明白是python的子线程如果不是daemon的话,主线程是不能响应任何中断的。但设为daemon后主线程会随之退出,接着整个进程很快就退出了,所以还需要在主线程中检测各个子线程的状态,直到所有子线程退出后自己才退出,因此上例29行之后的代码可以修改为:

threads=[]
for i in range(cc):
    t = threading.Thread(target=doStress, args=(i, cc))
    t.setDaemon(True)
    threads.append(t)
    t.start()
for i in range(cc):
    threads[i].join()
重新试一下,问题依然没有解决,进程还是没有响应Ctrl+C,这是因为join()函数同样会waiting在一个锁上,使主线程无法捕获信号。因此继续修改,调用线程的isAlive()函数判断线程是否完成:
while 1:
    alive = False
    for i in range(cc):
        alive = alive or threads[i].isAlive()
    if not alive:
    break


这样修改后,程序完全按照预想运行了:可以顺利的打印每个线程应该打印的所有数字,也可以中途用Ctrl+C终结整个进程。完整的代码如下:
#!/bin/env python
# -*- coding: utf-8 -*-
#filename: peartest.py

import threading, signal

is_exit = False

def doStress(i, cc):
    global is_exit
    idx = i
    while not is_exit:
        if (idx < 10000000):
            print "thread[%d]: idx=%d"%(i, idx)
            idx = idx + cc
        else:
            break
    if is_exit:
        print "receive a signal to exit, thread[%d] stop."%i
    else:
        print "thread[%d] complete."%i

def handler(signum, frame):
    global is_exit
    is_exit = True
    print "receive a signal %d, is_exit = %d"%(signum, is_exit)

if __name__ == "__main__":
    signal.signal(signal.SIGINT, handler)
    signal.signal(signal.SIGTERM, handler)
    cc = 5
    threads = []
    for i in range(cc):
    t = threading.Thread(target=doStress, args=(i,cc))
    t.setDaemon(True)
    threads.append(t)
    t.start()
    while 1:
        alive = False
        for i in range(cc):
            alive = alive or threads[i].isAlive()
        if not alive:
            break

其实,如果用python写一个服务,也需要这样,因为负责服务的那个线程是永远在那里接收请求的,不会退出,而如果你想用Ctrl+C杀死整个服务,跟上面的压力测试程序是一个道理。

总结一下,python多线程中要响应Ctrl+C的信号以杀死整个进程,需要:

1.把所有子线程设为Daemon; 
2.使用isAlive()函数判断所有子线程是否完成,而不是在主线程中用join()函数等待完成; 
3.写一个响应Ctrl+C信号的函数,修改全局变量,使得各子线程能够检测到,并正常退出。


转载于: http://m.oschina.net/blog/125099

<think>嗯,用户现在问的是如何在Tkinter中按下按键停止绑定的事件。之前他们问过动态更新Label的问题,现在可能是在做更复杂的交互,比如开始一个循环任务,然后需要按键停止。 首先,我需要回忆一下Tkinter的事件处理机制。用户可能已经用after方法启动了定时任务,比如更新Label的内容,现在想要通过按键来停止这个循环。常见的做法应该是维护一个标志变量或者取消after的调度。 用户提到的“停止绑定事件”,可能是指如何解除已经绑定的事件处理函数,或者是停止由after方法定期触发的函数。需要明确,按键事件和停止after循环之间的关系。 可能需要两种情况:一种是停止一个正在循环执行的after任务,另一种是解绑某个事件处理函数,比如停止响应某个按键的press事件。用户的问题更可能是前者,因为之前示例中用了after来更新界面。 接下来,我需要考虑如何实现按键停止after循环。通常的做法是在after的回调函数中设置一个停止条件,比如当按下某个键时,改变一个标志变量,使得回调函数不再继续调度自身。 例如,在update_time函数中,每次执行时检查是否有一个stop_flag,如果为True,则不再调用after。然后,绑定一个按键事件,当按下比如空格键时,设置stop_flag为True。 另一种方法是使用root.after_cancel来取消已经调度的after任务,这需要保存after返回的id。例如,在启动循环时,保存任务ID,当按键触发时,调用after_cancel并传入该ID。 用户可能需要具体的代码示例,所以应该提供两种方法的例子。另外,需要注意线程安全,Tkinter的GUI操作必须在主线程进行,所以如果在多线程环境下,可能需要通过队列等方式传递信号,但这里可能不需要,因为用户的问题可能是在单线程下的处理。 同时,用户可能对Tkinter的变量作用域不太清楚,比如在函数内部如何访问和修改全局变量,或者使用类来封装状态。在示例中使用全局变量可能更简单,但也可以提到使用类的方式更优雅。 还需要考虑用户可能的错误,比如忘记保存after的ID,导致无法正确取消任务,或者在回调函数中未正确检查停止条件,导致循环无法终止。所以示例中需要明确这些步骤。 总结一下,回答需要包括两种方法:使用标志变量控制循环,和使用after_cancel取消任务。同时给出代码示例,说明如何绑定按键事件来触发停止操作,并解释关键点,比如全局变量的使用、after_cancel的必要性等。</think>在Tkinter中实现按键停止事件绑定,主要有两种典型方法。以下是具体实现方案: --- ### 方法一:解除事件绑定(推荐) ```python import tkinter as tk def start_listening(): # 绑定事件 root.bind('<Key>', key_press) label.config(text="监听中...") def stop_listening(): # 解除绑定 root.unbind('<Key>') label.config(text="已停止监听") def key_press(event): label.config(text=f"按下: {event.keysym}") root = tk.Tk() label = tk.Label(root, text="按S开始/停止监听", font=('Arial', 14)) label.pack(padx=20, pady=20) # 绑定切换监听状态的快捷键(S键) root.bind('s', lambda e: stop_listening() if root.bind('<Key>') else start_listening()) root.mainloop() ``` --- ### 方法二:通过标志位控制 ```python import tkinter as tk class KeyController: def __init__(self): self.active = False self.root = tk.Tk() self.label = tk.Label(self.root, text="按空格开始/停止", font=('Arial', 14)) self.label.pack(padx=20, pady=20) # 绑定空格键切换状态 self.root.bind('<space>', self.toggle) self.root.mainloop() def toggle(self, event): self.active = not self.active if self.active: self.start() else: self.stop() def start(self): self.root.bind('<Key>', self.show_key) # 绑定普通按键事件 self.label.config(text="监听中...") def stop(self): self.root.unbind('<Key>') # 解绑普通按键事件 self.label.config(text="已停止") def show_key(self, event): self.label.config(text=f"当前按键:{event.keysym}") KeyController() ``` --- **关键机制说明:** 1. `unbind()`方法:通过指定事件序列解除绑定 - 格式:`widget.unbind(sequence)` - 示例:`root.unbind('<KeyPress-a>')` 解除特定按键绑定 2. 标志位控制:通过布尔变量控制事件是否响应 ```python if self.allow_events: # 执行事件处理逻辑 ``` 3. 动态切换:可配合菜单栏/按钮实现"开始监听/停止监听"切换 --- **典型应用场景:** 1. 游戏开发中的暂停功能 2. 实时键盘输入监控的启停控制 3. 防止重复触发的输入限制 --- **扩展技巧:** - 使用`event.keysym`获取按键名称(如'Return', 'Escape') - 组合键处理:`<Control-Shift-KeyPress-A>`表示Ctrl+Shift+A - 可通过`event.state`判断修饰键状态(Shift/Ctrl/Alt等)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值