如何让Python脚本以类似C语言fork()的方式在后台持续运行?
os.fork() (Like C's fork()) Absolutely! If you’re familiar with nohup and & but want more granular control—mirroring C’s fork() behavior to make your Python app spawn a background child process and exit the parent foreground process—this is totally achievable. Python’s built-in os module gives you direct access to fork(), and we can build a proper daemonization workflow using it.
The Core Pattern: Double Fork + Process Isolation
To fully detach the child process from the terminal (so it keeps running even after the parent exits), we use a double fork approach. Here’s why it works:
- First fork: Creates a child process, then the parent exits immediately. This turns the child into an orphan, which gets adopted by the system’s init process (PID 1).
- Second fork: Creates a grandchild process, then the child exits. This ensures the grandchild isn’t a session leader, so it can’t reattach to any terminal.
Full Implementation Example
Here’s a complete, commented code snippet that implements this pattern correctly:
import os import sys def daemonize(): try: # First fork: Split off child process pid = os.fork() if pid > 0: # Parent process exits right away to leave child running sys.exit(0) except OSError as e: print(f"First fork failed: {e}", file=sys.stderr) sys.exit(1) # Now we're in the first child process # Detach from the parent's session and terminal os.setsid() # Reset umask to 0 so we have full control over file permissions os.umask(0) # Second fork: Create grandchild process try: pid = os.fork() if pid > 0: # First child exits, leaving grandchild as the daemon sys.exit(0) except OSError as e: print(f"Second fork failed: {e}", file=sys.stderr) sys.exit(1) # Now we're in the grandchild (daemon) process # Change working directory to root to avoid blocking mount points os.chdir("/") # Close all open file descriptors left from the parent for fd in range(os.sysconf("SC_OPEN_MAX")): try: os.close(fd) except OSError: pass # Redirect stdin, stdout, stderr to /dev/null to avoid terminal ties sys.stdin = open("/dev/null", "r") sys.stdout = open("/dev/null", "w") sys.stderr = open("/dev/null", "w") # Example usage if __name__ == "__main__": print("Starting daemon...") daemonize() # Your background task logic goes here import time while True: with open("/tmp/daemon_log.txt", "a") as f: f.write(f"Daemon running at {time.ctime()}\n") time.sleep(60)
Key Details Breakdown
os.fork(): Creates a copy of the current process. Returns the child PID to the parent, and 0 to the child—this is how we distinguish which process we’re in.os.setsid(): Creates a new session with the child as the leader, breaking all ties to the original terminal.os.umask(0): Resets the file creation mask so the daemon can create files with any permissions it needs (no inherited restrictions from the parent).- Closing file descriptors: Prevents the daemon from holding open references to the parent’s terminal or files, which could cause unexpected behavior.
- Redirecting stdio to
/dev/null: Ensures the daemon doesn’t accidentally write to the terminal or wait for user input, which would break its background status.
Why This Beats nohup/&
- Full programmatic control: You can customize daemonization logic (like logging to specific files, setting environment variables, or handling cleanup) directly in your code.
- Proper detachment: The double fork guarantees the process is fully decoupled from the terminal, avoiding issues like hanging sessions or accidental termination.
- Conditional daemonization: You can trigger the background behavior only when specific conditions are met, rather than relying on shell commands.
Alternative: Use the python-daemon Module
If you don’t want to roll your own implementation, the third-party python-daemon module handles all these edge cases (like signal handling) for you. Install it with pip install python-daemon, then use it like this:
import daemon import time def main(): # Your background task logic here while True: with open("/tmp/daemon_log.txt", "a") as f: f.write(f"Daemon running at {time.ctime()}\n") time.sleep(60) if __name__ == "__main__": with daemon.DaemonContext(): main()
This is cleaner and more robust for production use, as it abstracts away low-level process management details.
内容的提问来源于stack exchange,提问作者Melllvar

