关于python:threading忽略KeyboardInterrupt异常

threading ignores KeyboardInterrupt exception

我正在运行这个简单的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import threading, time

class reqthread(threading.Thread):    
    def run(self):
        for i in range(0, 10):
            time.sleep(1)
            print('.')

try:
    thread = reqthread()
    thread.start()
except (KeyboardInterrupt, SystemExit):
    print('
! Received keyboard interrupt, quitting threads.
'
)

但当我运行它时,它会打印出来

1
2
3
4
5
6
7
8
9
10
11
12
$ python prova.py
.
.
^C.
.
.
.
.
.
.
.
Exception KeyboardInterrupt in <module 'threading' from '/usr/lib/python2.6/threading.pyc'> ignored

实际上,python线程忽略了我的ctrl+c键盘中断,并且不打印Received Keyboard Interrupt。为什么?这个代码有什么问题?


尝试

1
2
3
4
5
6
7
8
9
try:
  thread=reqthread()
  thread.daemon=True
  thread.start()
  while True: time.sleep(100)
except (KeyboardInterrupt, SystemExit):
  print '
! Received keyboard interrupt, quitting threads.
'

如果不调用time.sleep,则主进程过早跳出try...except块,因此不会捕获KeyboardInterrupt。我的第一个想法是使用thread.join,但这似乎会阻塞主进程(忽略键盘中断),直到thread完成。

当主进程结束时,thread.daemon=True使线程终止。


为了总结评论中建议的更改,以下内容对我很有用:

1
2
3
4
5
6
7
8
9
10
try:
  thread = reqthread()
  thread.start()
  while thread.isAlive():
    thread.join(1)  # not sure if there is an appreciable cost to this.
except (KeyboardInterrupt, SystemExit):
  print '
! Received keyboard interrupt, quitting threads.
'

  sys.exit()


稍微修改Ubuntu的解决方案。

删除tread.daemon=true,如eric建议的那样,并用signal.pause()替换休眠循环:

1
2
3
4
5
6
7
8
9
import signal
try:
  thread=reqthread()
  thread.start()
  signal.pause() # instead of: while True: time.sleep(100)
except (KeyboardInterrupt, SystemExit):
  print '
! Received keyboard interrupt, quitting threads.
'


try ... except放在每根线上,把signal.pause()放在真正的main()上,对我来说是有用的。

不过,请注意导入锁。我猜这就是为什么python在默认情况下不解ctrl-c的原因。


我的(hacky)解决方案是像这样使用monkey-patch Thread.join()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def initThreadJoinHack():
  import threading, thread
  mainThread = threading.currentThread()
  assert isinstance(mainThread, threading._MainThread)
  mainThreadId = thread.get_ident()
  join_orig = threading.Thread.join
  def join_hacked(threadObj, timeout=None):
   """
    :type threadObj: threading.Thread
    :type timeout: float|None
   """

    if timeout is None and thread.get_ident() == mainThreadId:
      # This is a HACK for Thread.join() if we are in the main thread.
      # In that case, a Thread.join(timeout=None) would hang and even not respond to signals
      # because signals will get delivered to other threads and Python would forward
      # them for delayed handling to the main thread which hangs.
      # See CPython signalmodule.c.
      # Currently the best solution I can think of:
      while threadObj.isAlive():
        join_orig(threadObj, timeout=0.1)
    else:
      # In all other cases, we can use the original.
      join_orig(threadObj, timeout=timeout)
  threading.Thread.join = join_hacked