Search code examples
pythonpython-multiprocessing

If two multiprocessing can request input on the terminal, is there a way to pause one of them until the answer is given?


As can be seen in the code below, two multiprocessing runs together, but both have a moment that can ask for an input() in the Terminal, is there any way to pause the other multiprocessing until the answer is given in the Terminal?

File Code_One archaic and simple example to speed up the explanation:

from time import sleep

def main():
        sleep(1)
        print('run')
        sleep(1)
        print('run')
        sleep(1)
        input('Please, give the number:')

File Code_Two archaic and simple example to speed up the explanation:

from time import sleep

def main():
        sleep(2)
        input('Please, give the number:')
        sleep(1)
        print('run 2')
        sleep(1)
        print('run 2')
        sleep(1)
        print('run 2')
        sleep(1)
        print('run 2')
        sleep(1)
        print('run 2')

File Main_Code:

import Code_One
import Code_Two
import multiprocessing
from time import sleep

def main():
    while True:
        pression = multiprocessing.Process(target=Code_One.main)
        xgoals = multiprocessing.Process(target=Code_Two.main)
        pression.start()
        xgoals.start()
        pression.join()
        xgoals.join()
        print('Done')
        sleep(5)

if __name__ == '__main__':
    main()

How should I proceed in this situation?

In this example, as it doesn't pause the other multi, whenever it asks for an input this error happens:

input('Please, give the number:')
EOFError: EOF when reading a line

Solution

  • Sure, this is possible. To do it you will need to use some sort of interprocess communication (IPC) mechanism to allow the two processes to coordinate. time.sleep is not the best option though, and there are much more efficient ways of tackling it that are specifically made just for this problem.

    Probably the most efficient way is to use a multiprocessing.Event, like this:

    import multiprocessing
    import sys
    import os
    
    def Code_One(event, fno):
        proc_name = multiprocessing.current_process().name
        print(f'running {proc_name}')
        sys.stdin = os.fdopen(fno)
        val = input('give proc 1 input: ')
        print(f'proc 1 got input: {val}')
        event.set()
    
    
    def Code_Two(event, fno):
        proc_name = multiprocessing.current_process().name
        print(f'running {proc_name} and waiting...')
        event.wait()
        sys.stdin = os.fdopen(fno)
        val = input('give proc 2 input: ')
        print(f'proc 2 got input {val}')
    
    
    if __name__ == '__main__':
        event = multiprocessing.Event()
        pression = multiprocessing.Process(name='code_one', target=Code_One, args=(event, sys.stdin.fileno()))
        xgoals = multiprocessing.Process(name='code_two', target=Code_Two, args=(event, sys.stdin.fileno()))
        xgoals.start()
        pression.start()
        xgoals.join()
        pression.join()
    

    This creates the event object, and the two subprocesses. Event objects have an internal flag that starts out False, and can then be toggled True by any process calling event.set(). If a process calls event.wait() while the flag is False, that process will block until another process calls event.set().

    The event is created in the parent process, and passed to each subprocess as an argument. Code_Two begins and calls event.wait(), which blocks until the internal flag in the event is set to True. Code_One executes immediately and then calls event.set(), which sets event's internal flag to True, and allows Code_Two to proceed. At that point both processes have returned and called join, and the program ends.

    This is a little hacky because it is also passing the stdin file number from the parent to the child processes. That is necessary because when subprocesses are forked, those file descriptors are closed, so for a child process to read stdin using input it first needs to open the correct input stream (that is what sys.stdin = os.fdopen(fno) is doing). It won't work to just send sys.stdin to the child as another argument, because of the mechanics that Python uses to set up the environment for forked processes (sys.stdin is a IO wrapper object and is not pickleable).