Search code examples
pythonwhile-loopparallel-processingpython-multithreadingkeyboardinterrupt

Python, exit While loop with User Input using multithreading (cntrl+c wont work)


I have a system utilization function, monitor(), that I want to run until the user stops it, by typing the number Zero, exit_collecting(). I don't want to abruptly end the program because that would negate the subsequent code.
I tried running the two functions using multithreading, exit_collecting() and monitor(), letting monitor() run until the user stops it by typing Zero, exit_collecting().

My code below threw a pile of tracebacks. I am a newbie at this, any ideas, any help would be great. Thank you. BTW I originally attempted using "try with an except for KeyboardInterrupt", but when using the IDLE (Spyder), the ctrl-c combo doesn't work (its assigned to "copy"), ctrl-c didn't work when I ran it from the console in Linux either.

def exit_collecting():
    try:
        val = int(input("Type 0 to exit data collection mode"))
        if val == 0:
            flag = 0
    except:
        print(val,"typo, enter 0 to exit data collection mode")


def monitor():
    import time
    import psutil
    flag = 1
    while flag > 0:
        time.sleep(1)
        print("cpu usuage:",psutil.cpu_percent())
        

from multiprocessing import Process
p1 = Process(target=exit_collecting)
p1.start()
p2 = Process(target=monitor)    
p2.start()
p1.join()
p2.join()

Solution

  • Your version with multiprocessing is doomed to failure since each process has its own memory space and does not share the same variable flag. It can be done with multiprocessing but you must use an implementation of flag that uses shared memory.

    A solution using threading is far simpler and your code should work if only you make one change. You neglected to declare flag as global. This is required to ensure that functions exit_collecting and monitor are modifying the same variable. Without these declarations, each function is modifying a local flag variablle:

    def exit_collecting():
        global flag
        try:
            val = int(input("Type 0 to exit data collection mode"))
            if val == 0:
                flag = 0
        except:
            print(val,"typo, enter 0 to exit data collection mode")
    
    
    def monitor():
        global flag
        import time
        import psutil
        flag = 1
        while flag > 0:
            time.sleep(1)
            print("cpu usuage:",psutil.cpu_percent())
    
    
    from threading import Thread
    p1 = Thread(target=exit_collecting)
    p1.start()
    p2 = Thread(target=monitor)
    p2.start()
    p1.join()
    p2.join()
    

    But perhaps the above code can be simplified by making the monitor thread a daemon thread, that is a thread that it will automatically terminate when all non-daemon threads terminate (as currently written, it appears that function monitor can be terminated at any point in processing). But in any case, the main thread can perform the function that exit_collecting was performing. And there is no reason why you now can't use a keyboard interrupt (as long as you are waiting on an input statement in the main thread):

    def monitor():
        import time
        import psutil
        while True:
            time.sleep(1)
            print("cpu usuage:",psutil.cpu_percent())
    
    
    from threading import Thread
    p = Thread(target=monitor)
    p.daemon = True
    p.start()
    try:
        input("Input enter to halt (or ctrl-C) ...")
    except KeyboardInterrupt:
        pass         
    """
    When the main thread finishes all non-daemon threads have completed
    and therefore the monitor thread will terminate.
    """
    

    Update: Allow monitor thread to gracefully terminate and allow keyboard interrupt

    I have simplified the logic a but just to use a simple global flag, terminate_flag, initially False, that is only read by the monitor thread and therefore does not have to explicitly declare it as Global:

    terminate_flag = False
    
    def monitor():
        import time
        import psutil
        while not terminate_flag:
            time.sleep(1)
            print("cpu usuage:", psutil.cpu_percent())
    
    
    from threading import Thread
    
    p = Thread(target=monitor)
    p.start()
    try:
        input('Hit enter or ctrl-c to terminate ...')
    except KeyboardInterrupt:
        pass
    terminate_flag = True # tell monitor it is time to terminate
    p.join() # wait for monitor to gracefully terminate