4

The python module threading has an object Thread to be used to run processes and functions in a different thread. This object has a start method, but no stop method. What is the reason a Thread cannot be stopped my calling a simple stop method? I can imagine cases when it is unconvenient to use the join method...

Alex
  • 41,580
  • 88
  • 260
  • 469

5 Answers5

10

start can be generic and make sense because it just fires off the target of the thread, but what would a generic stop do? Depending upon what your thread is doing, you could have to close network connections, release system resources, dump file and other streams, or any number of other custom, non-trivial tasks. Any system that could do even most of these things in a generic way would add so much overhead to each thread that it wouldn't be worth it, and would be so complicated and shot through with special cases that it would be almost impossible to work with. You can keep track of all created threads without joining them in your main thread, then check their run state and pass them some sort of termination message when the main thread shuts itself down though.

Silas Ray
  • 25,682
  • 5
  • 48
  • 63
  • 3
    Followup question: Normally you can stop a python program by pressing CTRL-C. What is the difference to a thread in this case? Why is it not forbidden to stop a program with CTRL-C, although all of your arguments still hold? – Alex Jan 23 '13 at 15:11
  • 1
    That's a system event that by default cuases an exception that closes your program. As @Anony-Mousse said in his answer, you'd have to capture that event and handle it in a custom way to have your threads clean-up properly. Also, if you are using CTRL-C as the standard way to end your programs, especially if you aren't handling the event in a custom way already, then you are just asking for bugs. Having the system kill a process is something that should only really happen when there is a problem, or when you are doing something like restarting the machine. – Silas Ray Jan 23 '13 at 15:13
8

It is definitely possible to implement a Thread.stop method as shown in the following example code:

import threading
import sys

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()

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

import time

def main():
    test = Thread2(target=printer)
    test.start()
    time.sleep(1)
    test.stop()
    test.join()

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

if __name__ == '__main__':
    main()

The Thread3 class appears to run code approximately 33% faster than the Thread2 class.


Addendum:

With sufficient knowledge of Python's C API and the use of the ctypes module, it is possible to write a far more efficient way of stopping a thread when desired. The problem with using sys.settrace is that the tracing function runs after each instruction. If an asynchronous exception is raised instead on the thread that needs to be aborted, no execution speed penalty is incurred. The following code provides some flexibility in this regard:

#! /usr/bin/env python3
import _thread
import ctypes as _ctypes
import threading as _threading

_PyThreadState_SetAsyncExc = _ctypes.pythonapi.PyThreadState_SetAsyncExc
# noinspection SpellCheckingInspection
_PyThreadState_SetAsyncExc.argtypes = _ctypes.c_ulong, _ctypes.py_object
_PyThreadState_SetAsyncExc.restype = _ctypes.c_int

# noinspection PyUnreachableCode
if __debug__:
    # noinspection PyShadowingBuiltins
    def _set_async_exc(id, exc):
        if not isinstance(id, int):
            raise TypeError(f'{id!r} not an int instance')
        if not isinstance(exc, type):
            raise TypeError(f'{exc!r} not a type instance')
        if not issubclass(exc, BaseException):
            raise SystemError(f'{exc!r} not a BaseException subclass')
        return _PyThreadState_SetAsyncExc(id, exc)
else:
    _set_async_exc = _PyThreadState_SetAsyncExc


# noinspection PyShadowingBuiltins
def set_async_exc(id, exc, *args):
    if args:
        class StateInfo(exc):
            def __init__(self):
                super().__init__(*args)

        return _set_async_exc(id, StateInfo)
    return _set_async_exc(id, exc)


def interrupt(ident=None):
    if ident is None:
        _thread.interrupt_main()
    else:
        set_async_exc(ident, KeyboardInterrupt)


# noinspection PyShadowingBuiltins
def exit(ident=None):
    if ident is None:
        _thread.exit()
    else:
        set_async_exc(ident, SystemExit)


class ThreadAbortException(SystemExit):
    pass


class Thread(_threading.Thread):
    def set_async_exc(self, exc, *args):
        return set_async_exc(self.ident, exc, *args)

    def interrupt(self):
        self.set_async_exc(KeyboardInterrupt)

    def exit(self):
        self.set_async_exc(SystemExit)

    def abort(self, *args):
        self.set_async_exc(ThreadAbortException, *args)
Noctis Skytower
  • 21,433
  • 16
  • 79
  • 117
5

Killing threads in a reliable fashion is not very easy. Think of the cleanups required: which locks (that might be shared with other threads!) should automatically be released? Otherwise, you will easily run into a deadlock!

The better way is to implement a proper shutdown yourself, and then set

mythread.shutdown = True
mythread.join()

to stop the thread.

Of course your thread should do something like

while not this.shutdown:
    continueDoingSomething()
releaseThreadSpecificLocksAndResources()

to frequently check for the shutdown flag. Alternatively, you can rely on OS-specific signaling mechanisms to interrupt a thread, catch the interrupt, and then cleanup.

The cleanup is the most important part!

Has QUIT--Anony-Mousse
  • 76,138
  • 12
  • 138
  • 194
  • cannot do that if you have `asyncore.loop()` running in your thread. – Alex Jan 23 '13 at 14:41
  • There are good reasons to do it this way. Because you *do* want to have the thread stop and *unlock* any resources it has. Otherwise, you'll probably run into a deadlock sooner or later. – Has QUIT--Anony-Mousse Jan 23 '13 at 14:44
  • But how to handle a `asyncore.loop()` running in the thread? How to stop that nicely? (I guess this is a different question...) – Alex Jan 23 '13 at 14:45
  • Alex: Yes it's probably separate question. Looks like someone has already asked it for you :) http://stackoverflow.com/questions/10490077/how-to-quit-an-asyncore-dispatcher-from-a-handler. Try setting a flag to indicate that the loop should shutdown, and triggering a handler which checks for it. This seems like a common solution to your problem. I quite often have a separate thread that is blocked on some queue, so to shut them down I put a special value into the queue or have it check a flag after every get. – monk Jan 23 '13 at 15:10
  • Ha, I found the same question even before I asked this main question. I am right now trying to implement it. Thanks anyway. – Alex Jan 23 '13 at 15:13
1

Stopping a thread should be up to the programmer to implement. Such as designing your thread to check it there are any requests for it to terminate immediately. If python (or any threading language) allowed you to just stop a thread then you would have code that just stopped. This is bug prone, etc.

Imagine if your thread as writing output to a file when you killed/stopped it. Then the file might be unfinished and corrupt. However if you simple signaled the thread you wanted it to stop then it could close the file, delete it, etc. You, the programmer, decided how to handle it. Python can't guess for you.

I'd suggest reading up on multi-threading theory. A decent start: http://en.wikipedia.org/wiki/Multithreading_(software)#Multithreading

Kansha
  • 570
  • 4
  • 12
0

On some platforms you can't forcibly "stop" a thread. It's also bad to do it since then the thread won't be able to clean up allocated resources. And it might happen when the thread is doing something important, like I/O.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621