Wilbert
Wilbert

Reputation: 1818

Python threads, thread is not informed to stop

I would like to stop a Python thread when the main program stops. It is for a class that connects to a server. The connection is maintained by the background thread, the foreground thread answers to callbacks. The following is a minimal example.

#!/usr/bin/python
import time, threading
class test():
    running = False
    def __init__(self):
        print "init"
        self.running = True
        self.thread = threading.Thread(target = self.startThread)
        self.thread.start()

    def __del__(self):
        running = False
        print "del"

    def startThread(self):
        print "thread start"
        while self.running:
            time.sleep(1)
            print "thread running"

a = test()

When the program ends, I would naively expect __del__() to be called so that the background thread can be informed to stop, but i is not called untill after the background thread stops. Explicitly calling some function is not an option since the class is used by other people, whom I do not want to force to use some extra lines of code.

Upvotes: 5

Views: 6809

Answers (4)

heine
heine

Reputation: 597

I was facing the same problem and finally found the answer to this question in the answer to another question.

Use the daemon = True flag. Documentation for 3.x and 2.x

So your code would be this:

#!/usr/bin/python
import time, threading

class test():
    def __init__(self):
        print "init"
        self.thread = threading.Thread(target = self.startThread)
        self.thread.daemon = True
        self.thread.start()

    def startThread(self):
        print "thread start"
        while True:
            time.sleep(1)
            print("thread running")

a = test()

Remark: this is propably not cleanly ending your thread, but it stops it when you exit your program, I guess this is what you were looking for.

Upvotes: 1

knight
knight

Reputation: 435

You forgot to change the "running" variable of your current test() instance, in your del function. It should be

 def __del__(self):
        self.running = False
        print "del"

That should do the trick.

Upvotes: 0

Austin Phillips
Austin Phillips

Reputation: 15756

As per gnibbler's comment, it may be better to use a context manager for explicit resource deallocation. Opinions appear to vary on whether __del__ should be used for resource deallocation. A couple of good posts on the subject are here and here.

If you're used to a language like C++ where RAII is used, it's sometimes a bit difficult to get accustomed to the idea that destructors in Python may not be called when you expect, if at all, usually because of how reference counting and garbage collection works.

So, the usual approach in Python is to use a context manager which can be used to provide explicit deallocation of resource.

A simple threading example might look like this (untested):

#!/usr/bin/python
import time, threading

class test():
    def __init__(self):
        print "init"
        self.stop_event = threading.Event()
        self.thread = threading.Thread(target = self.startThread)
        self.thread.start()

    def startThread(self):
        print "thread start"
        while not self.stop_event.isSet():
            time.sleep(1)
            print "thread running"

    def close(self):
        # Request thread to stop.
        self.stop_event.set()
        # Wait for thread to exit.
        self.thread.join()

    def __enter__(self):
        # Nothing to do, thread already started.  Could start
        # thread here to enforce use of context manager.

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

The test() class is then used like this in a context manager:

with test():
    # Thread will be active here.
    pass

# Thread will have been stopped and joined.

or, use the Python contextlib.closing helper function which will ensure that close is called on exit.

import contextlib

with contextlib.closing(test()):
    # Thread will be active here.
# But not here

Upvotes: 1

Armin Rigo
Armin Rigo

Reputation: 12900

__del__ is not called as long as there are references to self, and you have one such reference in the background thread itself: in the self argument of def startThread(self):.

You need to move the function that runs the background thread outside of the class. And rather than a __del__ I would recommend for example using a weakref, as follows. This code should work without the __del__() method and without using a self.running attribute.

self.thread = threading.Thread(target=run_me, args=(weakref.ref(self),))
...
def run_me(weak_self):
    "Global function"
    while True:
        self = weak_self()
        if self is None: break    # no more reference to self
        ...
        del self        # forget this reference for now
        time.sleep(1)

Upvotes: 3

Related Questions