是否有可能终止一个正在运行的线程而不设置/检查任何标志/信号/等等?
当前回答
Pieter Hintjens (ØMQ-project的创始人之一)表示,使用ØMQ并避免使用锁、互斥对象、事件等同步原语,是编写多线程程序最明智、最安全的方法:
http://zguide.zeromq.org/py:all#Multithreading-with-ZeroMQ
这包括告诉子线程,它应该取消它的工作。这可以通过为线程配备ØMQ-socket并轮询该套接字以获得一条表示它应该取消的消息来实现。
该链接还提供了一个使用ØMQ的多线程python代码示例。
其他回答
没有官方API来做这个,没有。
你需要使用平台API来杀死线程,例如pthread_kill或TerminateThread。你可以访问这样的API,例如通过pythonwin,或通过ctypes。
注意,这本质上是不安全的。这可能会导致不可收集的垃圾(来自堆栈帧的局部变量,成为垃圾),并可能导致死锁,如果被杀死的线程在被杀死时具有GIL。
你不应该在没有与线程合作的情况下强行终止线程。
杀死一个线程消除了try/finally阻塞设置的任何保证,所以你可能会让锁锁定,文件打开等等。
唯一可以认为强制终止线程是一个好主意的情况是快速终止程序,但绝不是单个线程。
多处理。进程可以p.terminate()
如果我想杀死一个线程,但不想使用标志/锁/信号/信号量/事件/任何东西,我就把线程提升到完整的进程。对于只使用几个线程的代码,开销并没有那么糟糕。
例如,这可以方便地终止执行阻塞I/O的助手“线程”
转换很简单:在相关代码中替换所有线程。多线程线程。进程和所有队列。多处理队列。排队并将p.t terminate()所需的调用添加到想要杀死子进程p的父进程中
关于多处理,请参阅Python文档。
例子:
import multiprocessing
proc = multiprocessing.Process(target=your_proc_function, args=())
proc.start()
# Terminate the process
proc.terminate() # sends a SIGTERM
在Python和任何语言中,突然终止线程通常都是一种糟糕的模式。考虑以下情况:
线程持有一个必须正确关闭的关键资源 线程创建了其他几个线程,这些线程也必须被杀死。
如果你能负担得起(如果你在管理自己的线程),处理这个问题的好方法是有一个exit_request标志,每个线程都会定期检查它,看看它是否到了退出的时候。
例如:
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, *args, **kwargs):
super(StoppableThread, self).__init__(*args, **kwargs)
self._stop_event = threading.Event()
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
在这段代码中,当您希望线程退出时,应该在线程上调用stop(),并使用join()等待线程正确退出。线程应该定期检查停止标志。
然而,在某些情况下,您确实需要终止一个线程。例如,当您正在包装一个外部库时,该库正忙于长时间的调用,并且您希望中断它。
下面的代码允许(有一些限制)在Python线程中引发异常:
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 an 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 exception in the context of the
thread represented by this instance.
"""
_async_raise( self._get_my_tid(), exctype )
(根据Tomer Filiba的《Killable Threads》改编。关于PyThreadState_SetAsyncExc返回值的引用似乎来自旧版本的Python。)
正如文档中所指出的,这不是一个神奇的子弹,因为如果线程在Python解释器之外忙,它将不会捕获中断。
这段代码的一个良好使用模式是让线程捕获特定的异常并执行清理。这样,您可以中断任务,但仍然可以进行适当的清理。
虽然它相当古老,但对一些人来说这可能是一个方便的解决方案:
一个扩展线程模块功能的小模块—— 允许一个线程在另一个线程的上下文中引发异常 线程。通过触发SystemExit,你最终可以杀死python线程。
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.
推荐文章
- Numpy Max vs amax vs maximum
- 我应该在.gitignore文件中添加Django迁移文件吗?
- 每n行有熊猫
- 实例属性attribute_name定义在__init__之外
- 如何获取在Python中捕获的异常的名称?
- 第一次出现的值大于现有值的Numpy
- 如何从Python函数中返回两个值?
- 前一个月的Python日期
- Python中方括号括起来的列表和圆括号括起来的列表有什么区别?
- Python日志记录不输出任何东西
- 每n秒运行特定代码
- SQLAlchemy是否有与Django的get_or_create等价的函数?
- 如何将python datetime转换为字符串,具有可读格式的日期?
- 美丽的汤和提取div及其内容的ID
- 在Python中重置生成器对象