class=””>关于python:有没有办法杀死一个线程?

Is there any way to kill a Thread?

是否可以在不设置/检查任何标志/信号量的情况下终止运行线程?


在Python和任何语言中,突然断线都是一种糟糕的模式。想想下面的例子:

  • 线程持有一个必须正确关闭的关键资源。
  • 该线程还创建了其他几个必须终止的线程。

如果您能负担得起(如果您管理自己的线程),那么处理这个问题的好方法是设置一个exit-request标志,每个线程定期检查该标志,看看是否该退出。

例如:

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

class StoppableThread(threading.Thread):
   """Thread class with a stop() method. The thread itself has to check
    regularly for the stopped() condition."""


    def __init__(self):
        super(StoppableThread, self).__init__()
        self._stop_event = threading.Event()

    def stop(self):
        self._stop_event.set()

    def stopped(self):
        return self._stop_event.is_set()

在这段代码中,当您希望线程退出时,应该在该线程上调用stop(),并等待该线程使用join()正确退出。线程应定期检查停止标志。

但是,有些情况下您确实需要终止线程。例如,当您包装一个外部库时,该库正忙于长时间调用,并且您想中断它。

以下代码(有一些限制)允许在Python线程中引发异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def _async_raise(tid, exctype):
    '''Raises an exception in the threads with id tid'''
    if not inspect.isclass(exctype):
        raise TypeError("Only types can be raised (not instances)")
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid),
                                                     ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        #"if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"
        ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), None)
        raise SystemError("PyThreadState_SetAsyncExc failed")

class ThreadWithExc(threading.Thread):
    '''A thread class that supports raising exception in the thread from
       another thread.
    '''

    def _get_my_tid(self):
       """determines this (self's) thread id

        CAREFUL : this function is executed in the context of the caller
        thread, to get the identity of the thread represented by this
        instance.
       """

        if not self.isAlive():
            raise threading.ThreadError("the thread is not active")

        # do we have it cached?
        if hasattr(self,"_thread_id"):
            return self._thread_id

        # no, look for it in the _active dict
        for tid, tobj in threading._active.items():
            if tobj is self:
                self._thread_id = tid
                return tid

        # TODO: in python 2.6, there's a simpler way to do : self.ident

        raise AssertionError("could not determine the thread's id")

    def raiseExc(self, exctype):
       """Raises the given exception type in the context of this thread.

        If the thread is busy in a system call (time.sleep(),
        socket.accept(), ...), the exception is simply ignored.

        If you are sure that your exception should terminate the thread,
        one way to ensure that it works is:

            t = ThreadWithExc( ... )
            ...
            t.raiseExc( SomeException )
            while t.isAlive():
                time.sleep( 0.1 )
                t.raiseExc( SomeException )

        If the exception is to be caught by the thread, you need a way to
        check that your thread has caught it.

        CAREFUL : this function is executed in the context of the
        caller thread, to raise an excpetion in the context of the
        thread represented by this instance.
       """

        _async_raise( self._get_my_tid(), exctype )

(基于Tomer Filiba的可杀线。关于PyThreadState_SetAsyncExc返回值的引用似乎来自于Python的旧版本。)

如文档中所述,这不是一个神奇的子弹,因为如果线程在Python解释器之外很忙,它就不会捕获中断。

此代码的一个好的使用模式是让线程捕获特定的异常并执行清理。这样,您就可以中断一个任务,并且仍然可以进行适当的清理。


没有官方的API可以做到这一点。

您需要使用平台API来终止线程,例如pthread_kill或terminateThread。您可以通过pythonwin或ctypes访问此类API。

注意这是固有的不安全因素。如果被杀死的线程在被杀死时拥有gil,那么它很可能会导致无法回收的垃圾(来自变成垃圾的堆栈帧的局部变量),并可能导致死锁。


一个multiprocessing.Processp.terminate()

在我想杀死一个线程,但不想使用标志/locks/signals/signals/events/whatever的情况下,我会将线程升级为完整的进程。对于只使用几个线程的代码,开销并没有那么糟糕。

例如,这很容易终止执行阻塞I/O的助手"线程"。

转换很简单:在相关代码中,将所有threading.Thread替换为multiprocessing.Process,将所有queue.Queue替换为multiprocessing.Queue,并将所需的p.terminate()调用添加到要杀死其子p的父进程中。

Python博士


如果您试图终止整个程序,可以将线程设置为"守护进程"。看见线程守护进程


这是基于thread2——可杀死线程(python配方)

您需要调用pythreadstate_setAsyncExc(),它只能通过CTypes使用。

这只在Python2.7.3上进行了测试,但它很可能与最近的其他2.x版本一起使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import ctypes

def terminate_thread(thread):
   """Terminates a python thread from another thread.

    :param thread: a threading.Thread instance
   """

    if not thread.isAlive():
        return

    exc = ctypes.py_object(SystemExit)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
        ctypes.c_long(thread.ident), exc)
    if res == 0:
        raise ValueError("nonexistent thread id")
    elif res > 1:
        #"""if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")


如果不配合线程,则不应强制终止线程。

终止一个线程会删除Try/Finally块设置的任何保证,从而使锁保持锁定、文件保持打开状态等。

唯一一次你可以争辩说强制杀死线程是个好主意,那就是快速杀死一个程序,但不要杀死单个线程。


正如其他人提到的,规范是设置一个停止标志。对于轻量级的东西(没有线程的子类化,没有全局变量),lambda回调是一个选项。(注意if stop()中的括号。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import threading
import time

def do_work(id, stop):
    print("I am thread", id)
    while True:
        print("I am thread {} doing something".format(id))
        if stop():
            print("  Exiting loop.")
            break
    print("Thread {}, signing off".format(id))


def main():
    stop_threads = False
    workers = []
    for id in range(0,3):
        tmp = threading.Thread(target=do_work, args=(id, lambda: stop_threads))
        workers.append(tmp)
        tmp.start()
    time.sleep(3)
    print('main: done sleeping; time to stop the threads.')
    stop_threads = True
    for worker in workers:
        worker.join()
    print('Finis.')

if __name__ == '__main__':
    main()

用总是刷新(sys.stdout.flush()pr()函数替换print()可以提高shell输出的精度。

(仅在windows/eclipse/python3.3上测试)


在Python中,不能直接杀死线程。

如果你真的不需要线程!!),您可以使用多处理包。在这里,要杀死一个进程,只需调用方法:

1
yourProcess.terminate()  # kill the process!

python将终止您的进程(在Unix上通过sigterm信号,而在Windows上通过TerminateProcess()调用)。使用队列或管道时请注意使用!(它可能会损坏队列/管道中的数据)

注意,multiprocessing.Eventmultiprocessing.Semaphore的工作方式与threading.Eventthreading.Semaphore的工作方式完全相同。事实上,第一个是拿铁的复制品。

如果你真的需要使用线程,就没有办法直接杀死它。但是,您可以使用"守护进程线程"。实际上,在python中,线程可以标记为守护进程:

1
yourThread.daemon = True  # set the Thread as a"daemon thread"

当没有活动的非守护进程线程时,主程序将退出。换句话说,当主线程(当然是非守护进程线程)完成其操作时,即使仍有一些守护进程线程在工作,程序也将退出。

注意,在调用start()方法之前,必须将线程设置为daemon

当然,您可以也应该使用daemon,即使使用multiprocessing。在这里,当主进程退出时,它将尝试终止其所有守护子进程。

最后,请注意,sys.exit()os.kill()不是选项。


通过将跟踪安装到将退出线程的线程中,可以杀死线程。请参阅附件一个可能的实现。

用Python杀死线程


如果你不剪断一根线就更好了。一种方法是在线程的循环中引入一个"try"块,并在您想要停止线程时抛出一个异常(例如break/return/…)。这会使您的/while/…)。我在我的应用程序上使用过这个,它可以工作…


如下面的示例代码所示,绝对可以实现Thread.stop方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import sys
import threading
import time


class StopThread(StopIteration):
    pass

threading.SystemExit = SystemExit, StopThread


class Thread2(threading.Thread):

    def stop(self):
        self.__stop = True

    def _bootstrap(self):
        if threading._trace_hook is not None:
            raise ValueError('Cannot run thread with tracing!')
        self.__stop = False
        sys.settrace(self.__trace)
        super()._bootstrap()

    def __trace(self, frame, event, arg):
        if self.__stop:
            raise StopThread()
        return self.__trace


class Thread3(threading.Thread):

    def _bootstrap(self, stop_thread=False):
        def stop():
            nonlocal stop_thread
            stop_thread = True
        self.stop = stop

        def tracer(*_):
            if stop_thread:
                raise StopThread()
            return tracer
        sys.settrace(tracer)
        super()._bootstrap()

###############################################################################


def main():
    test1 = Thread2(target=printer)
    test1.start()
    time.sleep(1)
    test1.stop()
    test1.join()
    test2 = Thread2(target=speed_test)
    test2.start()
    time.sleep(1)
    test2.stop()
    test2.join()
    test3 = Thread3(target=speed_test)
    test3.start()
    time.sleep(1)
    test3.stop()
    test3.join()


def printer():
    while True:
        print(time.time() % 1)
        time.sleep(0.1)


def speed_test(count=0):
    try:
        while True:
            count += 1
    except StopThread:
        print('Count =', count)

if __name__ == '__main__':
    main()

Thread3类运行代码的速度似乎比Thread2类快33%。


1
2
3
from ctypes import *
pthread = cdll.LoadLibrary("libpthread-2.15.so")
pthread.pthread_cancel(c_ulong(t.ident))

t是你的Thread对象。

阅读python源代码(Modules/threadmodule.cPython/thread_pthread.h可以看到Thread.identpthread_t类型,所以可以在python中使用libpthread做任何pthread可以做的事情。


以下解决方法可用于终止线程:

1
2
3
4
5
6
7
8
9
10
11
kill_threads = False

def doSomething():
    global kill_threads
    while True:
        if kill_threads:
            thread.exit()
        ......
        ......

thread.start_new_thread(doSomething, ())

这甚至可以用于终止线程,这些线程的代码是从主线程写入另一个模块中的。我们可以在该模块中声明一个全局变量,并使用它终止在该模块中生成的线程。

我通常使用它在程序退出时终止所有线程。这可能不是终止线程的完美方法,但可能会有所帮助。


我想补充的一点是,如果您在线程lib python中阅读官方文档,建议您在不希望线程突然结束时,避免使用"恶魔"线程,并使用paolo rovelli提到的标志。

官方文件:

Daemon threads are abruptly stopped at shutdown. Their resources (such as open files, database transactions, etc.) may not be released properly. If you want your threads to stop gracefully, make them non-daemonic and use a suitable signaling mechanism such as an Event.

我认为创建daemonic线程取决于您的应用程序,但总的来说(在我看来),最好避免杀死它们或使它们成为daemonic。在多处理中,您可以使用is_alive()检查处理状态,并"终止"以完成它们(同时还可以避免gil问题)。但在Windows中执行代码时,有时会发现更多的问题。

并且始终记住,如果您有"活动线程",那么python解释器将运行等待它们。(因为这个恶魔可以帮助你,如果不重要突然结束)。


我已经很晚了,但我一直在努力解决类似的问题,下面的内容似乎都能很好地解决这个问题,并让我在守护进程的子线程退出时进行一些基本的线程状态检查和清理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import threading
import time
import atexit

def do_work():

  i = 0
  @atexit.register
  def goodbye():
    print ("'CLEANLY' kill sub-thread with value: %s [THREAD: %s]" %
           (i, threading.currentThread().ident))

  while True:
    print i
    i += 1
    time.sleep(1)

t = threading.Thread(target=do_work)
t.daemon = True
t.start()

def after_timeout():
  print"KILL MAIN THREAD: %s" % threading.currentThread().ident
  raise SystemExit

threading.Timer(2, after_timeout).start()

产量:

1
2
3
4
0
1
KILL MAIN THREAD: 140013208254208
'CLEANLY' kill sub-thread with value: 2 [THREAD: 140013674317568]

如果您显式地调用time.sleep()作为线程的一部分(比如轮询一些外部服务),那么philipe方法的一个改进就是在eventwait()方法中使用超时,无论您在何处使用sleep()方法。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import threading

class KillableThread(threading.Thread):
    def __init__(self, sleep_interval=1):
        super().__init__()
        self._kill = threading.Event()
        self._interval = sleep_interval

    def run(self):
        while True:
            print("Do Something")

            # If no kill signal is set, sleep for the interval,
            # If kill signal comes in while sleeping, immediately
            #  wake up and handle
            is_killed = self._kill.wait(self._interval)
            if is_killed:
                break

        print("Killing Thread")

    def kill(self):
        self._kill.set()

然后运行它

1
2
3
4
5
6
t = KillableThread(sleep_interval=5)
t.start()
# Every 5 seconds it prints:
#: Do Something
t.kill()
#: Killing Thread

使用wait()而不是sleep()ing并定期检查事件的好处是,您可以在较长的睡眠间隔中编程,线程几乎立即停止(否则您将是sleep()ing),我认为处理退出的代码要简单得多。


有一个为此目的而建的图书馆,别说了。尽管这里列出的一些相同的警告仍然适用,但至少这个库提供了一种常规的、可重复的技术来实现所述的目标。


这似乎适用于Windows7上的pywin32。

1
2
3
my_thread = threading.Thread()
my_thread.start()
my_thread._Thread__stop()

This is a bad answer, see the comments

方法如下:

1
2
3
4
5
6
7
8
9
10
from threading import *

...

for thread in enumerate():
    if thread.isAlive():
        try:
            thread._Thread__stop()
        except:
            print(str(thread.getName()) + ' could not be terminated'))

给它几秒钟,然后你的线程应该停止。同时检查thread._Thread__delete()方法。

为了方便起见,我建议采用thread.quit()方法。例如,如果您的线程中有一个套接字,我建议您在socket handle类中创建一个quit()方法,终止该套接字,然后在quit()的内部运行一个thread._Thread__stop()


用setdaemon(true)启动子线程。

1
2
3
4
5
6
7
8
9
10
11
def bootstrap(_filename):
    mb = ModelBootstrap(filename=_filename) # Has many Daemon threads. All get stopped automatically when main thread is stopped.

t = threading.Thread(target=bootstrap,args=('models.conf',))
t.setDaemon(False)

while True:
    t.start()
    time.sleep(10) # I am just allowing the sub-thread to run for 10 sec. You can listen on an event to stop execution.
    print('Thread stopped')
    break

虽然它相当古老,但对于一些人来说,这可能是一个方便的解决方案:

A little module that extends the threading's module functionality --
allows one thread to raise exceptions in the context of another
thread. By raising SystemExit, you can finally kill python threads.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import threading
import ctypes    

def _async_raise(tid, excobj):
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(excobj))
    if res == 0:
        raise ValueError("nonexistent thread id")
    elif res > 1:
        #"""if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
        raise SystemError("PyThreadState_SetAsyncExc failed")

class Thread(threading.Thread):
    def raise_exc(self, excobj):
        assert self.isAlive(),"thread must be started"
        for tid, tobj in threading._active.items():
            if tobj is self:
                _async_raise(tid, excobj)
                return

        # the thread was alive when we entered the loop, but was not found
        # in the dict, hence it must have been already terminated. should we raise
        # an exception here? silently ignore?

    def terminate(self):
        # must raise the SystemExit type, instead of a SystemExit() instance
        # due to a bug in PyThreadState_SetAsyncExc
        self.raise_exc(SystemExit)

因此,它允许"线程在另一个线程的上下文中引发异常",这样,终止的线程就可以处理终止,而无需定期检查中止标志。

但是,根据其原始源代码,此代码存在一些问题。

  • The exception will be raised only when executing python bytecode. If your thread calls a native/built-in blocking function, the
    exception will be raised only when execution returns to the python
    code.

    • There is also an issue if the built-in function internally calls PyErr_Clear(), which would effectively cancel your pending exception.
      You can try to raise it again.
  • Only exception types can be raised safely. Exception instances are likely to cause unexpected behavior, and are thus restricted.

    • For example: t1.raise_exc(TypeError) and not t1.raise_exc(TypeError("blah")).
    • IMHO it's a bug, and I reported it as one. For more info, http://mail.python.org/pipermail/python-dev/2006-August/068158.html
  • I asked to expose this function in the built-in thread module, but since ctypes has become a standard library (as of 2.5), and this
    feature is not likely to be implementation-agnostic, it may be kept
    unexposed.

Pieter Hintjens——的创始人之一?MQ项目——说,使用?MQ和避免同步原语(如锁、互斥锁、事件等)是编写多线程程序最明智和最安全的方法:

http://zguide.zeromq.org/py:all使用zeromq进行多线程处理

这包括告诉子线程,它应该取消它的工作。这可以通过在螺纹上安装一个?MQ套接字,并在该套接字上轮询一条消息,表明它应该取消。

该链接还提供了一个关于多线程python代码的示例。MQ。


可以在进程中执行命令,然后使用进程ID将其杀死。我需要在两个线程之间同步,其中一个线程本身不会返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
processIds = []

def executeRecord(command):
    print(command)

    process = subprocess.Popen(command, stdout=subprocess.PIPE)
    processIds.append(process.pid)
    print(processIds[0])

    #Command that doesn't return by itself
    process.stdout.read().decode("utf-8")
    return;


def recordThread(command, timeOut):

    thread = Thread(target=executeRecord, args=(command,))
    thread.start()
    thread.join(timeOut)

    os.kill(processIds.pop(), signal.SIGINT)

    return;

如果您真的需要杀死子任务的能力,请使用备用实现。multiprocessinggevent都支持不分青红皂白地杀死"线程"。

Python的线程不支持取消。甚至不要尝试。您的代码很可能死锁、损坏或泄漏内存,或者具有其他意想不到的难以调试的"有趣"效果,这些效果很少发生,而且不具有确定性。