6

I have a function foo that only stops once a condition has been met. While foo is running, I need to ask for user input the whole time (keeps asking for user input). I want them to run separately without interfering with each other.

In my example below, foo keeps printing 'Hello' and getUserInput keeps looking for user input. I want foo to keep printing hello even if i do not enter anything for user input. It will keep asking for input as long as the user does not enter letter 'e'. I have my attempt below:

import threading
from time import sleep

class test:
    def __init__(self):
        self.running = True

    def foo(self):
        while(self.running):
            print 'Hello\n'
            sleep(2)

    def getUserInput(self):
        x = ''
        while(x != 'e'):
            x = raw_input('Enter value: ')
        self.running = False

    def go(self):
        th1 = threading.Thread(target=self.foo)
        th2 = threading.Thread(target=self.getUserInput)
        th1.start()
        th2.start()


t = test()
t.go()

My code prints out the first hello and asks for input but nothing after that. What am I doing wrong? Thanks for your help in advance.

Aasam Tasaddaq
  • 645
  • 4
  • 13
  • 23
  • If you don't enter anything at all, does it still keep printing 'hello'? – Aasam Tasaddaq Sep 11 '12 at 19:09
  • Which version of python are you running? – Aasam Tasaddaq Sep 11 '12 at 19:11
  • 2.6.1 on OSX 10.6.8. Also works on 2.5.4 and 2.7.2. – Keith Randall Sep 11 '12 at 19:13
  • Well thats just strange for me then. I have 32 bit python, 2.7.2, on windows 7 (64 bit, some of my modules were acting incompatible with 64 bit hence 32 bit python). Thanks for your input by the way. – Aasam Tasaddaq Sep 11 '12 at 19:15
  • Works fine here on python 2.7.2 FreeBSD amd64 (64 bit). – Roland Smith Sep 11 '12 at 20:09
  • I ran it on another machine and I encountered the same problem. Maybe I am not clarifying my objective/goal properly. I want both threads to run independent of each other where you have 'hello' print out every 2 seconds regardless of the input/or no input. Currently, Its not printing the next 'hello' unless I do enter some input. – Aasam Tasaddaq Sep 12 '12 at 17:15

1 Answers1

7

Update: The opener was running his code on Windows in IDLE. Regarding I/O it behaves differently than a shell or the Windows command line. His code works on the Windows command line.

In principle, your code works for me. I am running Python 2.6.5.

Several comments here:

1) In your case it would be fine to only have two threads: the main thread and another one. However, it will also work with three. It's just that your main thread does nothing else than waiting for the other threads to finish.

2) You should to explicitly join() all threads you spawn. You do this in the main thread before terminating it. Keep record of the threads you spawn (e.g. in a list threads) and then join them at the end of your program (e.g. for t in threads: t.join()).

3) You share the variable self.running between threads. It is fine in this case, as one thread only reads it and another one only writes it. In general, you need to be very careful with shared variables and acquire a lock before changing it.

4) You should catch the KeyboardInterrupt exception in the main thread and find a way to communicate to your other threads to terminate :)

5) Use lowercase method names, so instead of getUserInput call it get_user_input. Use uppercase class names and inherit from object: class Test(object):

This is a running example:

import threading
from time import sleep


def main():
    t = Test()
    t.go()
    try:
        join_threads(t.threads)
    except KeyboardInterrupt:
        print "\nKeyboardInterrupt catched."
        print "Terminate main thread."
        print "If only daemonic threads are left, terminate whole program."


class Test(object):
    def __init__(self):
        self.running = True
        self.threads = []

    def foo(self):
        while(self.running):
            print '\nHello\n'
            sleep(2)

    def get_user_input(self):
        while True:
            x = raw_input("Enter 'e' for exit: ")
            if x.lower() == 'e':
               self.running = False
               break

    def go(self):
        t1 = threading.Thread(target=self.foo)
        t2 = threading.Thread(target=self.get_user_input)
        # Make threads daemonic, i.e. terminate them when main thread
        # terminates. From: http://stackoverflow.com/a/3788243/145400
        t1.daemon = True
        t2.daemon = True
        t1.start()
        t2.start()
        self.threads.append(t1)
        self.threads.append(t2)


def join_threads(threads):
    """
    Join threads in interruptable fashion.
    From http://stackoverflow.com/a/9790882/145400
    """
    for t in threads:
        while t.isAlive():
            t.join(5)


if __name__ == "__main__":
    main()

When typing e or E, the program ends after a short delay (as intended by you). When pressing ctrl+c, it immediately terminates. Making a program that uses threading responsive to exceptions is a bit trickier than expected. I have included important references in the source above.

This is how it looks like during runtime:

$ python supertest.py

Hello

Enter 'e' for exit: 
Hello


Hello


Hello

e
$
Dr. Jan-Philip Gehrcke
  • 33,287
  • 14
  • 85
  • 130