Pergunta

Before you read on, know that I'm new to Python and very new to threading so forgive me if I misunderstand how threads work or make a rookie error :P

Short description of my goal:

  1. The main thread (a) does some things (e.g. prints "begin!")
  2. Main thread spawns a new thread (b) that first prints "Thread B started" and then prints x+1 forever (1, 2, 3 ...)
  3. Main thread prints "Woop!"
  4. Then the end of the main thread is reached, it terminates itself and then switches to thread b making b the main thread
  5. Program is now running thread b as the main thread so is just printing x+1 forever and a has been forgotten and is no longer relevant
  6. Ctrl+C will terminate thread b and effectively, the whole program will be terminated because thread a doesn't exist anymore

Here's what I have so far (the basics):

import threading, time

def printCount():
    print "Thread B started"
    x = 0
    while True:
        time.sleep(1)
        x = x + 1
        print x

## User Code ##
print "begin!"
threadB = threading.Thread(target=printCount)
threadB.start()
print "woop!"

The requirements are:

  • I don't want to modify below the 'User Code' mark much at all. I certainly don't want to wrap it in a class, a function or it's own thread
  • Pressing Ctrl+C at any point should terminate the entire program with no threads left running (Using something like: except
    KeyboardInterrupt: os._exit(1))
    inside User Code is fine
  • Thread a may continue to run instead of making thread b the main thread, but in this case, I don't want the code to handle Ctrl+C terminating the entire program inside the User Code section

This example is not my actual goal, just a simplified version of the problem I'm having. I'm trying to make an IRC framework where the user can import it and use the API very simply without messying their own code with threads and interrupts and the like. This is why it's important for the user code to be as clean as possible.

The framework would allow the user to create an IRC bot which runs forever, listening to commands whilst allowing the user to add their own commands. The Github link is here if you're interested (It's very WIP atm).

Foi útil?

Solução

You cannot "switch" threads. So once you're done with your main thread, you have to wait for the other threads to terminate using the method join. But note this:

  • Since the join method is not interruptible with KeyboardInterrupt, you need to specify a timeout and loop over to detect user interrupts.
  • Since you can't force a thread to terminate, you have to implement a stop mecanism using threading.Event for instance
  • You also need to use threading.Lock to prevent concurrent access on shared resources, like sys.stdout (used when you print)

I gathered these aspects in a class called ThreadHandler, please have a look:

import threading, time

def printCount(lock, stop):
    with lock:
        print "Thread B started"
    x = 0
    while not stop.is_set():
        time.sleep(1)
        x = x + 1
        with lock:
            print x

class ThreadHandler():
    STEP = 0.2

    def __init__(self, target):
        self.lock = threading.Lock()
        self.stop = threading.Event()
        args = (self.lock, self.stop)
        self.thread = threading.Thread(target=target, args=args)

    def start(self):
        self.thread.start()

    def join(self):
        while self.thread.is_alive():
            try:
                self.thread.join(self.STEP)
            except KeyboardInterrupt:
                self.stop.set()

## User Code ##
print "begin!"
handler = ThreadHandler(target=printCount)
handler.start()
with handler.lock:
    print "woop!"
handler.join()

Outras dicas

Wrote a short note on another question yesterday having similar issues, this is a check you could implement in the subthread "b":

Instead of while 1: do the following:

def printCount():
    main = None
    for t in threading.enumerate():
        if t.name == 'MainThread':
            main = t
    print "Thread B started"
    x = 0
    while main and main.isAlive():
        time.sleep(1)
        x = x + 1
        print x

It would be a good idea to store main in the global scope for all threads to use isntead of having to look up the main thread each and every initation of the sub-thread. But this would do the work in your example.

main will be the handle towards your main thread by iterating through all threads (.enumerate()) and then placing the thread called "MainThread" into main and then calling main.isAlive() to check if it's still running. if main is None or False or if .isAlive() returns False it will indicate that the thread is either non-existant or dead, shutting down your subthread :)

You can't switch threads like that. It doesn't work like that.

However you could use signals with a global flag ALIVE:

import threading, time, signal

ALIVE = True

def handle_sigint(signum, frame):
    global ALIVE
    ALIVE = False

signal.signal(signal.SIGINT, handle_sigint)

def printCount():
    print "Thread B started"
    x = 0
    while ALIVE:  # <--- note the change
        time.sleep(1)
        x = x + 1
        print x

## User Code ##
print "begin!"
threadB = threading.Thread(target=printCount)
threadB.start()
print "woop!"

signal.pause()  # <--- wait for signals

Now it will gracefully quit after pressing CTRL+C.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top