4.1.1. Handling Signals¶
When a signal is received by a process they can either be ignored or caught resulting in code from a specified signal handler function being run. Execution continues after the signal handler is finished. Ignored signals may result in termination of the process.

Signals other that SIGKILL and SIGSTOP may be caught and execute a defined signal handler function.
In Unix, there is a kill
system call and a kill
command for use from a
Unix shell (command prompt). They both send signals to processes. The most
commonly used signals are listed below.
Name | Number | Description |
---|---|---|
SIGHUP | 1 | Hangup |
SIGINT | 2 | Terminal interrupt (Ctrl-C) |
SIGQUIT | 3 | Terminal quit (Ctrl-\) |
SIGKILL | 9 | Kill (can’t be caught or ignored) |
SIGUSR1 | 10 | User defined signal 1 |
SIGUSR2 | 12 | User defined signal 2 |
SIGTERM | 15 | Software termination signal (default of kill) |
SIGSTOP | 19 | Stop executing (can’t be caught or ignored) |
SIGTSTP | 20 | Terminal stop signal (Ctrl-Z) |
SIGBREAK | ? | Windows only (Ctrl-Break) |
In the following C program, we see how processes in Unix (Linux) can define
signal handlers with the signal
system call and also send signals to other
processes with the kill
system call.
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
static void sig_handler(int);
int main ()
{
int i, parent_pid, child_pid, status;
if(signal(SIGUSR1, sig_handler) == SIG_ERR)
printf("Parent: Unable to create handler for SIGUSR1\n");
if(signal(SIGUSR2, sig_handler) == SIG_ERR)
printf("Parent: Unable to create handler for SIGUSR2\n");
parent_pid = getpid();
if((child_pid = fork()) == 0) {
kill(parent_pid, SIGUSR1);
for (;;) pause();
}
else {
kill(child_pid, SIGUSR2);
sleep(3);
printf("Parent: Terminating child ...\n");
kill(child_pid, SIGTERM);
wait(&status);
printf("done\n");
}
}
static void sig_handler(int signo) {
switch(signo) {
case SIGUSR1: /* Incoming SIGUSR1 */
printf("Parent: Received SIGUSER1\n");
break;
case SIGUSR2: /* Incoming SIGUSR2 */
printf("Child: Received SIGUSER2\n");
break;
default: break;
}
return;
}
4.1.1.1. Keyboard Signals¶
Any signal may be sent to a process with the kill
system call and kill
shell command. The name kill is used because the most common usage is to
terminate a process and a signal that is not caught with a handler may
terminate the process.
A few signals may be sent to the foreground process by pressing certain key combinations on a keyboard. In Unix, these are:
- Ctrl + C:
- SIGINT – It should terminate the process. When it is caught, the handler usually attempts to stop the process gracefully.
- Ctrl + \ :
- SIGQUIT – Similar usage as SIGINT.
- Ctrl + Z:
- SIGTSTP – It is usually not caught. It does not terminate the process, but
puts the process in a stopped state. The
jobs
command lists stopped processes. From a stopped state, a process may be restarted in either the foreground or background (fg
andbg
commands), or terminated.
In Windows, console processes that are usually started from a terminal (cmd or power shell) are sent SIGINT with Ctrl-C or SIGBREAK with Ctrl-BREAK. But not all keyboards now have a BREAK key. Signals are not sent to non-console processes. If a program, such as a Python script, is run inside an IDE, such as Spyder or PyCharm, it is the IDE that sends the signal to the Python process.
Ctrl-D is often thought of as a signal because it serves as the ending
input of data supplied to programs such as cat
and a shell. It is actually
the end-of-file (EOF) character, not a signal. Programs that read input
from the keyboard or a file are informed that the end of data has been reached
when EOF is processed.
4.1.1.2. Common Uses of Signals¶
SIGHUP This is an important signal for Unix systems administrators. Most network server programs that run as a daemon (servers for the web, e-mail, DNS, FTP, etc.) will catch SIGHUP and reread their configuration files. So, for example, if a feature needs to be added or changed on the web server, but the server needs to continue running, then after changes are made to the configuration files, the change is activated with the command kill -HUP pid, where pid is the process id number of the web server.
- SIGKILL
- This signal sent to a process is the most likely to kill the process. If someone runs a program with an infinite loop or some other bug, it can be difficult to kill the process. Only the owner of the process or the root user has permission to send a signal to a process. So when a Unix systems administrator is asked to stop a rogue process, kill -9 pid is the tool of choice.
- SIGTERM
- If the
kill
command is used to send a signal to a process without specifying which signal to send, then SIGTERM is sent. It will usually terminate the process.
4.1.1.3. Signals in Python¶
Python’s signal
module has a signal
method that creates a signal
handler. See the example code below.
The os
module has a kill
method for sending signals to
other processes.
As a convenience to programmers, if no signal handler is created for SIGINT
(Ctrl-C) then a KeyboardInterrupt
exception is raised upon receiving
SIGINT. A try ... except
block as shown below can catch the exception. With
no exception or signal handler, a received SIGINT will usually terminate the
process. Note that a signal handler will catch a SIGINT that is received while
any of the code is running while the KeyboardInterrupt
will only be caught
in a try ... except
block.
try:
some code
...
except KeyboardInterrupt:
your handler code
4.1.1.4. When do signals work as desired?¶
Signals almost always work as expected in Unix (Linux). Signals are one of the primary mechanism in Unix to communicate with running processes.
With any operating system, there is concern about signals not getting to interpreted scripts, such as Python, when run from within an IDE, such as Spyder. The only instance that I have found where this occurs is with SIGTSTP (Ctrl-Z). However, because of why SIGTSTP is used, it is not usually caught by programs. Spyder interprets Ctrl-Z as a command to the editor (undo) rather than as SIGTSTP. I have read several places in online programming forums where people suggest that an IDE may not pass Ctrl-C to a program as SIGINT because Ctrl-C is the editor command for copy. I have not observed this happening in either Linux or Windows.
Windows was not designed to make use of signals in the way that Unix does.
Windows has a taskkill
system call that will terminate a process, but it
does not use signals. The taskkill
system call is invoked by the Task
Manager and could be programmed into an application. Python’s os.kill
method for sending signals has many instances where it does not work as desired
in Windows and will likely be deprecated and removed in a later release.
Windows itself only sends SIGINT and SIGBREAK (if you have a BREAK key) signals
to console applications. It also appears, at least with Python, that there are
instances when no signals or keyboard data are sent to a process.
The example below illustrates when (Ctrl-C) has no impact on a Python
process when certain code is run in Windows. At the end of the example script
is a loop for join()
operations from the parent thread to each child
thread. In Windows, the (Ctrl-C) key sequence does not result in SIGINT
for either a signal handler or the KeyboardInterrupt
exception while the
joins are running. It does not matter if the script is run from a console or
within Spyder. In contrast, joins do not impact receiving SIGINT in Linux with
either a shell or within Spyder. However, before the joins is a loop of
sleep
statements. In this section of the code, both a signal handler and
the KeyboardInterrupt
exception work fine with Windows from either the
console or in Spyder. From my testing, I am convinced that Windows does not
receive any keyboard input while some system calls, such as a thread join, are
running.
Many questions, but few real answers, can be found in the online programming forums about signals not working as expected in Windows. The frustrating failures are found with all programming languages. Signal and exception handlers can be used and may work at times in Windows, but when they don’t work and you want to stop a Python script that is not ending on its own, just close the console window, or restart the Python kernel in the IDE.
4.1.1.5. Signals in Multithreaded Processes¶
When a signal is sent to a multithreaded process, only the parent thread can
receive the signal. In the example below, either a signal handler or the
KeyboardInterrupt
exception handler (if a SIGINT handler is not created)
sets a threading event to be true.
When each thread observes the event, then they can exit. The threads need to
first get to the code that checks the event, which can cause a delay before
a thread will finish.
# -*- coding: utf-8 -*-
"""
Demonstration and test of signals sent to a multi-threaded process.
"""
import threading
import time
import signal
exit_event = threading.Event()
def thread_function(thread_num):
global print_mutex
while True:
if exit_event.is_set():
print_mutex.acquire()
print(f"Thread {thread_num} exiting")
print_mutex.release()
break
else:
time.sleep(1)
def signal_handler(signum, frame):
print(f'Got signal {signum}. The program will end soon.')
exit_event.set()
#
# The global code:
#
print_mutex = threading.Lock()
signal.signal(signal.SIGINT, signal_handler)
# Ctrl-C, works in Linux and sometimes in Windows
# signal.signal(signal.SIGQUIT, signal_handler)
# Ctrl-\, works in Linux but not in Windows
# signal.signal(signal.SIGTSTP, signal_handler)
# Ctrl-Z, works in a Linux shell but not in Spyder.
# Does not work in Windows.
#
# Now create and start some threads.
#
threads = []
for i in range(10):
t = threading.Thread(target = thread_function, args = [i])
t.start()
threads.append(t)
# The following loop does nothing useful. It is not needed in Linux.
# In Windows, either the KeyboardInterrupt or the signal handler (if used)
# can stop the program with Ctrl-C while this loop is running.
for i in range(20):
print_mutex.acquire()
print(f"Loop {i}")
print_mutex.release()
if exit_event.is_set():
break
try:
time.sleep(1)
except KeyboardInterrupt:
print('Got KeyboardInterrupt. The program will end soon.')
exit_event.set()
# In Linux, either the KeyboardInterrupt (Ctrl-C) or the signal handler
# can stop the program during the joins. In Windows, neither work.
try:
for t in threads:
t.join()
except KeyboardInterrupt:
print('Got KeyboardInterrupt. The program will end soon.')
exit_event.set()