Python Process Management — Core Concepts

What a process is

Every running program on your computer is a process. Each process has a unique PID (process ID), an owner (the user who started it), resource consumption (CPU, memory), and a parent process that created it.

Python provides two main tools for working with processes:

  • psutil for inspecting and managing existing processes
  • subprocess for launching and controlling new processes

Listing and inspecting processes

Iterating all processes

import psutil

for proc in psutil.process_iter(['pid', 'name', 'username', 'cpu_percent', 'memory_info']):
    info = proc.info
    mem_mb = info['memory_info'].rss / (1024 * 1024) if info['memory_info'] else 0
    print(f"PID {info['pid']:>6} | {info['name']:<25} | "
          f"User: {info['username']:<12} | Mem: {mem_mb:.1f} MB")

The attrs parameter in process_iter() tells psutil which fields to collect. This is more efficient than calling individual methods on each process, because psutil batches the system calls.

Getting detailed info on one process

proc = psutil.Process(1234)

print(f"Name: {proc.name()}")
print(f"Status: {proc.status()}")
print(f"Started: {proc.create_time()}")
print(f"CPU: {proc.cpu_percent(interval=1)}%")
print(f"Memory RSS: {proc.memory_info().rss / (1024**2):.1f} MB")
print(f"Threads: {proc.num_threads()}")
print(f"Open files: {len(proc.open_files())}")
print(f"Connections: {len(proc.connections())}")
print(f"Command: {' '.join(proc.cmdline())}")

Finding specific processes

By name

def find_by_name(name: str) -> list[psutil.Process]:
    return [p for p in psutil.process_iter(['name'])
            if p.info['name'] == name]

nginx_procs = find_by_name('nginx')

By resource usage

def top_memory_processes(count: int = 10) -> list[dict]:
    procs = []
    for p in psutil.process_iter(['pid', 'name', 'memory_info', 'cpu_percent']):
        if p.info['memory_info']:
            procs.append({
                'pid': p.info['pid'],
                'name': p.info['name'],
                'memory_mb': p.info['memory_info'].rss / (1024 ** 2),
                'cpu': p.info['cpu_percent'],
            })
    return sorted(procs, key=lambda x: x['memory_mb'], reverse=True)[:count]

By port

def find_by_port(port: int) -> list[dict]:
    """Find processes listening on a specific port."""
    results = []
    for conn in psutil.net_connections(kind='inet'):
        if conn.laddr.port == port and conn.status == 'LISTEN':
            try:
                proc = psutil.Process(conn.pid)
                results.append({
                    'pid': conn.pid,
                    'name': proc.name(),
                    'address': f"{conn.laddr.ip}:{conn.laddr.port}",
                })
            except psutil.NoSuchProcess:
                continue
    return results

Controlling processes

Sending signals

import signal

proc = psutil.Process(1234)

# Graceful termination
proc.terminate()  # Sends SIGTERM

# Wait for it to exit (with timeout)
try:
    proc.wait(timeout=10)
except psutil.TimeoutExpired:
    proc.kill()  # Force kill with SIGKILL

# Send a custom signal
proc.send_signal(signal.SIGUSR1)  # Trigger reload, etc.

Starting new processes

import subprocess

# Simple: run and wait
result = subprocess.run(
    ['python3', 'worker.py', '--id', '1'],
    capture_output=True, text=True, timeout=30
)

# Long-running: start and manage
proc = subprocess.Popen(
    ['python3', 'server.py'],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
)
print(f"Started with PID {proc.pid}")

# Later: check if still running
if proc.poll() is None:
    print("Still running")

Process trees

Processes have parent-child relationships. A web server might spawn worker processes, and each worker might spawn helpers:

def process_tree(pid: int, indent: int = 0):
    try:
        proc = psutil.Process(pid)
        mem = proc.memory_info().rss / (1024 ** 2)
        print(f"{'  ' * indent}PID {pid} | {proc.name()} | {mem:.1f} MB")
        for child in proc.children():
            process_tree(child.pid, indent + 1)
    except psutil.NoSuchProcess:
        pass

Common misconception

Many people use os.kill(pid, signal.SIGKILL) as their first choice to stop a process. This is like pulling the power cord — the process gets no chance to clean up (close files, save state, release locks). Always try SIGTERM first and give the process time to shut down gracefully. Use SIGKILL only as a last resort after a timeout.

Practical use cases

  • Runaway process killers that terminate processes exceeding CPU or memory thresholds
  • Port conflict resolution that finds and optionally kills processes occupying a needed port
  • Process restarters that detect a crashed service and restart it
  • Resource auditing that logs per-process resource usage for capacity planning
  • Deployment scripts that gracefully stop old versions before starting new ones
  • Health monitors that verify critical processes are running and alert when they are not

One thing to remember: Python’s process management capabilities let you build the same tools that system administrators use daily — listing processes, finding resource hogs, sending signals for graceful shutdown, and starting new processes — all programmable and automatable.

pythonsystem-administrationautomationprocesses

See Also