Process starts after I call a function even though I try to start the process first

Viewed 40

I'm trying to learn how to use multiple processes in Python and I encountered a problem similar to the example below. I try to start a process called p1 using .start() and after that to call a function do_something(). The problem is that the function is called before the process starts.

The code I used:

import time
from multiprocessing import Process


def complex_calculation():
    start_timer = time.time()
    print("Started calculating...")
    [x ** 2 for x in range(20000000)]  # calculation
    print(f"complex_calculation: {time.time() - start_timer}")


def do_something():
    print(input("Enter a letter: "))


if __name__ == "__main__":
    p1 = Process(target=complex_calculation)
    p1.start()
    do_something()
    p1.join()

It seems to work if I use time.sleep():

if __name__ == "__main__":
    p1 = Process(target=complex_calculation)
    p1.start()
    time.sleep(1)
    do_something()
    p1.join()

My questions are:

  1. Why does this happen?
  2. What can I do so that I don't have to use time.sleep() ?
1 Answers

As pointed out in the comments, multiple processes run concurrently. Without doing some extra work, there are never guarantees about the order in which the processes are scheduled to run by the operating system. So while you call p1.start() before do_something(), all that means is that the Python code related to starting the process has completed before do_something is run. But the actual process represented by p1 may run in any way relative to the remainder of the Python code. It can run entirely before, entirely after, or interleaved in any way with the remainder of the Python code. Relying on it being scheduled in any particular way is one definition of a race condition.

To control the way in which these processes run relative to one another, you need a synchronization primitive. There are many ways to synchronize processes, it just depends on what you want to accomplish. If you want to make sure that the complex_calculation function has started before do_something is called, an event is probably the simplest approach. For example:

import time
from multiprocessing import Process, Event


def complex_calculation(event):
    event.set()  # Set the event, notifying any process waiting on it
    start_timer = time.time()
    print("Started calculating...")
    [x ** 2 for x in range(20000000)]  # calculation
    print(f"complex_calculation: {time.time() - start_timer}")


def do_something(event):
    event.wait()  # Wait for `complex_calculation` to set the event
    print(input("Enter a letter: "))


if __name__ == "__main__":
    event = Event()
    p1 = Process(target=complex_calculation, args=(event,))
    p1.start()
    do_something(event)
    p1.join()

You should see something like:

$ python3 test.py
Started calculating...
Enter a letter: a
a
complex_calculation: 6.86732816696167
Related