杰瑞科技汇

Python signal.kill如何优雅终止进程?

Of course! In Python, the signal module provides a mechanism for handling signals. The term "kill" is a bit of a misnomer in this context because Python doesn't directly "kill" processes in the way the shell command kill does. Instead, Python allows you to send signals to processes (including itself) and handle signals that your process receives.

Python signal.kill如何优雅终止进程?-图1
(图片来源网络,侵删)

Here's a comprehensive breakdown covering sending signals, handling them, and the common pitfalls.


The Key Players: os.kill() and signal.signal()

To work with signals, you'll primarily use two functions from different modules:

  • os.kill(pid, sig): This is the function that sends a signal to another process (or a group of processes).

    • pid: The Process ID of the target process.
    • sig: The signal number (e.g., signal.SIGTERM) or the signal name (e.g., signal.SIGTERM).
    • Note: On Windows, os.kill() only supports signal.SIGTERM (terminate) and signal.CTRL_C_EVENT.
  • signal.signal(signum, handler): This is the function that sets up a handler for a signal within your current process.

    Python signal.kill如何优雅终止进程?-图2
    (图片来源网络,侵删)
    • signum: The signal you want to handle (e.g., signal.SIGINT).
    • handler: A function that will be called when the signal is received. It can be:
      • A Python function (a "signal handler").
      • signal.SIG_IGN: To ignore the signal.
      • signal.SIG_DFL: To restore the default behavior for the signal.

Sending a Signal to Another Process (os.kill)

This is the direct equivalent of using the kill command in your terminal.

Scenario: Let's create a script that runs for a while and then have another script send it a termination signal.

Script 1: target_process.py (The process to be killed)

import time
import os
import signal
print(f"Target process started with PID: {os.getpid()}")
print("I am running... Press Ctrl+C in the terminal to send SIGINT, or use another script to send SIGTERM.")
try:
    # Loop indefinitely until a signal is received
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    # This block is executed if the user presses Ctrl+C (SIGINT)
    print("\nReceived SIGINT (Ctrl+C). Shutting down gracefully.")
except Exception as e:
    print(f"\nAn unexpected error occurred: {e}")
print("Target process finished.")

Script 2: killer_script.py (The process that sends the signal)

import os
import signal
import time
import sys
def kill_target():
    # Get the PID of the target process.
    # In a real scenario, you might get this from a file, a database, or another IPC mechanism.
    # For this example, we'll hardcode it or pass it as a command-line argument.
    if len(sys.argv) != 2:
        print("Usage: python killer_script.py <target_pid>")
        sys.exit(1)
    target_pid = int(sys.argv[1])
    print(f"Attempting to send SIGTERM to process with PID: {target_pid}")
    try:
        # Send the SIGTERM signal
        os.kill(target_pid, signal.SIGTERM)
        print(f"Successfully sent SIGTERM to PID {target_pid}")
    except ProcessLookupError:
        print(f"Error: Process with PID {target_pid} not found.")
    except PermissionError:
        print(f"Error: Permission denied to kill PID {target_pid}.")
    except Exception as e:
        print(f"An error occurred: {e}")
if __name__ == "__main__":
    kill_target()

How to Run:

  1. Open a terminal and run the target process:

    Python signal.kill如何优雅终止进程?-图3
    (图片来源网络,侵删)
    python target_process.py

    You will see its PID printed. Let's say it's 12345.

  2. Open a second terminal and run the killer script, passing the PID:

    python killer_script.py 12345

What Happens:

  • The killer_script.py sends a SIGTERM signal to process 12345.
  • The target_process.py receives the signal. By default, SIGTERM causes a graceful shutdown. The try...except block will catch this, and the script will print its shutdown message and exit.

Handling Signals in Your Own Process (signal.signal)

This is about making your own program responsive to signals.

Common Signals:

  • signal.SIGINT: Interrupt signal (sent by pressing Ctrl+C). Default behavior is to terminate.
  • signal.SIGTERM: Termination signal. A request to terminate gracefully. Default behavior is to terminate.
  • signal.SIGUSR1 / signal.SIGUSR2: User-defined signals. You can use them for any custom purpose.

Example: Handling SIGINT and SIGTERM

import signal
import time
import sys
# A flag to control the main loop
shutdown_flag = False
def handle_shutdown(signum, frame):
    """
    This function is called when a SIGINT or SIGTERM is received.
    """
    global shutdown_flag
    signal_name = "SIGINT" if signum == signal.SIGINT else "SIGTERM"
    print(f"\nReceived {signal_name}. Shutting down gracefully...")
    shutdown_flag = True
def main():
    # Set up the signal handlers
    # We can use the same handler for multiple signals
    signal.signal(signal.SIGINT, handle_shutdown)
    signal.signal(signal.SIGTERM, handle_shutdown)
    print("Process running. Press Ctrl+C or use 'kill <pid>' to stop.")
    print("PID:", os.getpid())
    try:
        while not shutdown_flag:
            print("Working...", end='\r')
            time.sleep(1)
    finally:
        # This code runs regardless of how the loop was exited
        print("\nCleaning up resources...")
        print("Process has shut down.")
if __name__ == "__main__":
    main()

How to Run:

  1. Save the script as handler_example.py and run it:
    python handler_example.py
  2. Press Ctrl+C in the terminal. You will see the custom shutdown message.
  3. Or, find its PID (e.g., 12346) and use kill 12346 in another terminal. The same handler will be triggered.

The Most Important Rule: Signal Handlers Are Limited

You cannot do everything inside a signal handler. Due to the way Python is implemented (using a Global Interpreter Lock or GIL), signal handlers should be extremely simple.

DO NOT do this in a signal handler:

  • Call most Python library functions (especially I/O like print() is technically unsafe, though it often works in CPython).
  • Use try...except...finally blocks.
  • Use sys.exit().
  • Use any object that isn't "signal-safe".

What you CAN do in a signal handler:

  • Set a global flag (like shutdown_flag = True in the example above). This is the most common and recommended pattern.
  • Use a small number of "async-signal-safe" C-level functions (like os.write() to a file descriptor, or time.time()).

The best practice is to use the signal handler to simply set a flag and then check that flag in your main program loop to perform the actual shutdown logic.


Special Case: signal.pthread_kill for Threads

If you want to send a signal to a specific thread within your Python process, you can use signal.pthread_kill().

Important: This is only available on Unix-like systems (Linux, macOS) and requires the threading module.

import signal
import threading
import time
import os
def worker_thread():
    print(f"Worker thread (PID: {os.getpid()}, Thread ID: {threading.get_ident()}) started.")
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        # Note: Ctrl+C typically sends the signal to the main thread,
        # not this worker thread.
        print(f"Worker thread {threading.get_ident()} received a signal.")
def main():
    # Create a new thread
    thread = threading.Thread(target=worker_thread)
    thread.start()
    print(f"Main thread (PID: {os.getpid()}, Thread ID: {threading.get_ident()}) running.")
    print("Sending SIGUSR1 to the worker thread after 3 seconds...")
    # Let the thread run for a bit
    time.sleep(3)
    # Get the thread's native OS ID (the kernel-level thread ID)
    native_thread_id = thread.native_id
    print(f"Sending SIGUSR1 to native thread ID: {native_thread_id}")
    try:
        # Send a signal to the specific thread
        signal.pthread_kill(native_thread_id, signal.SIGUSR1)
    except (AttributeError, ValueError) as e:
        print(f"Could not send signal to thread: {e}")
        print("This might be due to the OS or Python version.")
    # Wait for the thread to finish
    thread.join()
    print("Main thread finished.")
if __name__ == "__main__":
    main()

Note: The behavior of signals in multi-threaded Python programs is complex. The default handler for many signals (like SIGINT) is set for the entire process, and it might only terminate the main thread. Use pthread_kill with caution.

分享:
扫描分享到社交APP
上一篇
下一篇