How to kill a Process in the middle of execution after using join()?

Viewed 359

I want to kill a certain process or all processes in the middle of its execution. My sample code is as follows. How can I do that? Here, I want to kill the processes based on the current time. If the time is divisible by 2, I want to kill the processes, otherwise not.

import time
from multiprocessing import Process


def runTests(a, b):
    time.sleep(10)
    return a + b


def main(kill_processes):
    print(kill_processes)
    processes = []

    for i in range(3):
        print(i)
        proc = Process(target=runTests, args=(2, 4,))
        processes.append(proc)
        proc.start()

    for proc in processes:
        proc.join()
        if kill_processes:
            print("killing")
            proc.terminate()


if __name__ == "__main__":
    if round(time.time()) % 2 == 0:
        main(True)
    else:
        main(False)
3 Answers

You can use multiprocessing.Event to signal termination condition to your child processes. Don't join child processes in the main process. Instead let main process and child processes run in their own loop. Check the termination condition in the main loop, and signal it using the multiprocessing.Event to the child processes.

The Event object is passed as an argument to child process. Child process continuously checks if the event is set, and stops its work if so. Main loop checks the termination condition and sets the Event if condition is met (in the below example main loop waits for Ctrl+c).

import multiprocessing as mp
import os
import time


def do_work(a, b, stop_event):
    while not stop_event.is_set():
        try:
            time.sleep(2)
            print(f"worker {os.getpid()}: working ...", a + b)
            a += 1
            b += 1
        except KeyboardInterrupt:
            print(f"worker {os.getpid()}: received SIGINT. ignore.")
            pass

    print(f"worker {os.getpid()}: stop_event is set. exit.")


if __name__ == "__main__":
    stop_event = mp.Event()
    procs = []
    for i in range(3):
        # p = mp.Process(target=do_work, args=(1, 2, stop_event), daemon=True)
        p = mp.Process(target=do_work, args=(1, 2, stop_event))
        p.start()
        procs.append(p)

    while True:
        try:
            print("main: waiting for termination signal")
            time.sleep(1)
        except KeyboardInterrupt:
            print("main: received termination signal")            
            stop_event.set()
            
            # wait for the processes to stop
            for p in procs:
                p.join()

            for p in procs:
                print(f"worker {p.pid} is terminated: {not p.is_alive()}")
            
            # exit the main loop
            break
    
    print("main: bye")

This illustrates how to do it by using multiple threads in a manner similar to what's in the answer to the question multiprocessing in stoppable multithreading that I suggested you have a look at.

Basically all that is going on is all the join() calls are done in separate threads, so they won't block the main thread in the main process — which makes it possible for it to kill them.

import threading
import time
from multiprocessing import Process


def runTests(a, b):
    time.sleep(10)
    return a + b

def create_process(lock, i):
    proc = Process(target=runTests, args=(2, 4,))
    print(f'{proc.name} created')
    proc.start()
    with lock:
        processes.append(proc)
    proc.join()


def main(kill_processes):
    global processes
    N = 3
    lock = threading.RLock()
    processes = []

    print(f'main({kill_processes=})')

    for i in range(N):
        thread = threading.Thread(target=create_process, args=(lock, i))
        thread.start()

    while True:  # Wait for all processes to have been created.
        with lock:
            if len(processes) == N:
                break
            else:
                time.sleep(.001)

    if kill_processes:
        print("Killing the processes")
        for proc in processes:
            proc.terminate()
            print(f'process {proc} terminated')



if __name__ == "__main__":
    main(True)

#    if round(time.time()) % 2 == 0:
#        main(True)
#    else:
#        main(False)

If you want to terminate based on time, use the timeout paramter of join. One way is to set a stop time and as each process is joined, use time remaining as its timeout.

import time
from multiprocessing import Process

def runTests(a, b):
    time.sleep(10)
    return a + b

def main(kill_processes):
    print(kill_processes)
    processes = []

    end = time.time() + 10 # wait 10 seconds

    for i in range(3):
        print(i)
        proc = Process(target=runTests, args=(2, 4,))
        processes.append(proc)
        proc.start()

    for proc in processes:
        if kill_processes:
            delta = end - time.time()
        else:
            delta = None
        proc.join(delta)
        if kill_processes:
            print("killing")
            proc.terminate()
            proc.join(1)
            if proc.is_alive():
                proc.kill()

if __name__ == "__main__":
    if round(time.time()) % 2 == 0:
        main(True)
    else:
        main(False)
Related