How to send a signal to the main thread in python without using join?
Asked Answered
S

2

11

I Am trying to send a signal from a child thread to the main thread in a multi-threaded program (cannot use multi-processes). Unfortunately even after exhausting all the reading materials available online (which I could find), I Am unable to get a clear idea of how to do so. I Am a beginner to signals AND to python so please bear with me and explain as you would to a novice. I cannot use the join method in the process, since I want both the threads to be running simultaneously. Here is the code that I found related to the topic here - http://pymotw.com/2/signal/#signals-and-threads and it doesn't really work for me.

import signal
import threading
import os
import time

def signal_handler(num, stack):
    print 'Received signal %d in %s' % (num, threading.currentThread())

signal.signal(signal.SIGUSR1, signal_handler)

def wait_for_signal():
    print 'Waiting for signal in', threading.currentThread()
    signal.pause()
    print 'Done waiting'

# Start a thread that will not receive the signal
receiver = threading.Thread(target=wait_for_signal, name='receiver')
receiver.start()
time.sleep(0.1)

def send_signal():
    print 'Sending signal in', threading.currentThread()
    os.kill(os.getpid(), signal.SIGUSR1)

sender = threading.Thread(target=send_signal, name='sender')
sender.start()
sender.join()

# Wait for the thread to see the signal (not going to happen!)
print 'Waiting for', receiver
signal.alarm(2)
receiver.join()

Please explain with a multi-threaded example if possible. Thanks in advance!

Several answered 6/8, 2014 at 8:8 Comment(0)
J
12

Signals and threads really, really don't play nice together.

Consider use an Event or other synchronization mechanism. The following example creates an 'event' object, then passes it to two threads. One waits for two seconds, then signals the other to print out a message then exit.

source

import threading, time

def flagger_thread(event):
    """
    wait for two seconds, then make 'event' fire
    """
    time.sleep(2)
    event.set()

def waiter_thread(event):
    print("Waiting for event")
    if event.wait(5):
        print("event set.")
    else:
        print("Timed out.")

stop_event = threading.Event()
threading.Thread(target=flagger_thread, args=[stop_event]).start()
threading.Thread(target=waiter_thread, args=[stop_event]).start()

# wait for all threads to exit
for t in threading.enumerate():
    if t != threading.current_thread():
        t.join()

output

Waiting for event
event set.
Jacelynjacenta answered 18/8, 2014 at 18:39 Comment(1)
I'm not sure that "signals and threads shouldn't mix" is good advice. Signalling a multithreaded program is more complicated than signalling a single-threaded one, but the interactions (and undefined/"stay away" areas) between signals and threads are pretty well understood--especially in Python, which simplifies things by moving signal checking exclusively onto the main thread in between interpreter ops (which it did even in 2014 when this was posted, though numerous bugs in this area have been fixed since then).Reefer
R
2

The issue is that your code is doing something other than your goal. Your goal is to "send a signal from a child thread to the main thread", but the example code you posted is listening for the signal in a child thread.

In your example code, there are three threads:

  1. The main thread, which dispatches things and calls alarm(2) (generally a bad idea if all you want to do is sleep(2)).
  2. The sender child thread which sends a signal.
  3. The receiver child thread which waits for a signal.

That's not going to work, because signals can only be received from the main thread in Python. This is a very hard constraint. It doesn't have any asterisks or "except ..." cases available--not even the temptingly named signal.pthread_kill.

Fortunately, your goal (handle a signal sent by a thread in the main thread) is easily achievable, like this:


import signal
import threading
import os
import time

def signal_handler(num, stack):
    print('Received signal %d in %s' % (num, threading.current_thread()))

signal.signal(signal.SIGUSR1, signal_handler)

def wait_for_signal():
    print('Waiting for signal in', threading.current_thread())
    signal.pause()
    print('Done waiting')

def send_signal():
    time.sleep(1)
    print('Sending signal in', threading.current_thread())
    os.kill(os.getpid(), signal.SIGUSR1)

sender = threading.Thread(target=send_signal, name='sender')
sender.start()
wait_for_signal()
sender.join()  # Optional, but can't hurt
Reefer answered 7/8, 2024 at 23:4 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.