哪些是子进程,哪些是主进程,哪些是守护进程,哪些是脚本的正常执行,脚本运行启动的进程与这些进程有什么关系
Python 进程模型详解:主进程、子进程、守护进程
在 Python 脚本环境中,理解 主进程、子进程、守护进程 以及它们与脚本执行的关系,对于编写健壮的并发程序、后台服务或系统工具至关重要。下面从 Python 的角度详细解释这些概念,并结合 os、subprocess、multiprocessing 等模块进行说明。
一、基本概念(Python 视角)
1. 主进程(Main Process)
定义:运行 Python 脚本的解释器进程(即 python your_script.py 启动的那个进程)。
特点:
- PID 可通过
os.getpid()获取; - 是所有子进程的"父进程"(除非子进程被重新附着);
- 脚本顶层代码(不在函数或类中的代码)在此进程中执行。
python
import os
print("主进程 PID:", os.getpid())
2. 子进程(Child Process)
Python 中创建子进程主要有以下几种方式:
(1) 使用 subprocess 模块(最常见)
用于启动外部命令或程序(如 ls, grep, 自定义可执行文件等)。
python
import subprocess
proc = subprocess.Popen(['sleep', '5'])
print("子进程 PID:", proc.pid)
Popen调用底层fork() + exec(),创建一个独立的子进程;- 子进程的 PPID = 主进程 PID;
- 若主进程退出前未调用
proc.wait()或proc.communicate(),子进程可能变成僵尸进程(短暂)或孤儿进程(被 init 接管后继续运行)。
(2) 使用 os.system() 或 os.popen()
python
os.system('echo hello') # 阻塞式创建子进程
- 内部也是
fork + exec; - 更简单但控制力弱,不推荐用于复杂场景。
(3) 使用 multiprocessing.Process(用于并行计算)
python
from multiprocessing import Process
import os
def worker():
print("子进程 PID:", os.getpid(), "PPID:", os.getppid())
p = Process(target=worker)
p.start()
p.join()
- 这是在 Python 内部创建另一个 Python 解释器进程;
- 也属于子进程,PPID = 主进程 PID;
- 常用于 CPU 密集型任务并行化。
✅ 注意:multiprocessing 创建的是同语言子进程,而 subprocess 通常用于跨语言/系统命令。
3. 守护进程(Daemon Process in Python)
在 Python 中,"守护进程"有两种含义,需区分:
(1) 系统级守护进程(Daemon Service)
如 Nginx、Redis 等长期运行、脱离终端的服务;
Python 脚本可以实现这类守护进程,但需手动处理:
- 第一次 fork → 父进程退出,子进程继续;
- 调用
os.setsid()创建新会话,脱离控制终端; - 第二次 fork → 确保不能重新打开终端;
- 重定向 stdin/stdout/stderr 到 /dev/null;
- 改变工作目录(如 /);
- 设置 umask。
示例(简化版):
python
import os
import sys
def daemonize():
try:
pid = os.fork()
if pid > 0:
sys.exit(0) # 父进程退出
except OSError:
sys.exit(1)
os.chdir("/")
os.setsid()
os.umask(0)
try:
pid = os.fork()
if pid > 0:
sys.exit(0) # 第二次 fork
except OSError:
sys.exit(1)
# 重定向标准流
with open('/dev/null', 'r') as f:
os.dup2(f.fileno(), sys.stdin.fileno())
with open('/dev/null', 'a+') as f:
os.dup2(f.fileno(), sys.stdout.fileno())
os.dup2(f.fileno(), sys.stderr.fileno())
# 此时已是守护进程
while True:
with open('/tmp/daemon.log', 'a') as f:
f.write("守护中...\n")
time.sleep(10)
📌 实际项目建议使用 python-daemon 库,避免重复造轮子。
(2) multiprocessing 中的"守护进程"(Daemon Flag)
这是 Python 特有的概念,与系统守护进程不同!
python
p = Process(target=worker)
p.daemon = True # 设置为守护子进程
p.start()
- 含义:当主进程退出时,自动终止该子进程;
- 不会阻止主进程结束;
- 不能创建自己的子进程(某些平台限制);
- 不是系统级守护进程,仍属于当前会话,PPID ≠ 1。
⚠️ 这是常见的混淆点!p.daemon = True ≠ 系统守护进程。
4. 脚本的正常执行
- 指 Python 解释器顺序执行脚本代码;
- 所有函数调用、变量操作都在主进程内存空间内完成;
- 不会自动创建新进程,除非显式调用 subprocess、multiprocessing 或 os.fork()(Unix only)。
二、进程关系图(Python 场景)
终端 (bash)
└─ python script.py ← 主进程 (PID=1234)
├─ subprocess.Popen(['sleep','10']) ← 子进程 (PPID=1234)
├─ multiprocessing.Process(worker) ← 子进程 (PPID=1234, 可设 daemon=True)
└─ (若手动 daemonize)
└─ python script.py (第二次 fork 后) ← 守护进程 (PPID=1)
三、如何查看 Python 进程关系?
bash
# 查看当前用户的 Python 进程树
pstree -p | grep python
# 或
ps -ef --forest | grep -A5 -B5 python
示例输出:
user 1234 1000 0 10:00 pts/0 00:00:00 python script.py
user 1235 1234 0 10:00 pts/0 00:00:00 \_ sleep 10
user 1236 1234 0 10:00 pts/0 00:00:00 \_ python script.py <-- multiprocessing 子进程
四、关键总结(Python 重点)
| 概念 | Python 实现方式 | 是否系统守护进程 | PPID |
|---|---|---|---|
| 主进程 | python script.py | 否 | 终端/shell |
| 子进程(外部命令) | subprocess.Popen() | 否 | 主进程 PID |
| 子进程(Python) | multiprocessing.Process() | 否 | 主进程 PID |
| 守护子进程(Python) | p.daemon = True | ❌ 否(仅自动终止) | 主进程 PID |
| 系统守护进程 | 手动双 fork + setsid + 重定向 | ✅ 是 | 1 (init) |
五、最佳实践建议
- 需要后台服务? → 使用 systemd 管理 Python 脚本,或用
python-daemon库实现守护。 - 需要并行任务? → 优先用 multiprocessing(CPU 密集)或 concurrent.futures。
- 调用外部命令? → 用
subprocess.run()(推荐)或Popen(需精细控制)。 - 避免僵尸进程 → 对 subprocess 调用
.wait()或使用with Popen(...) as p:。 - 不要混淆
daemon=True和系统守护进程。
如果你有具体的 Python 脚本(比如用了 subprocess 或 multiprocessing),我可以帮你分析其中的进程结构和潜在问题!
哪些是子线程,哪些是主线程,哪些是守护线程,哪些是脚本的正常执行,脚本运行启动的线程与这些线程有什么关系
要理清 主线程、子线程、守护线程、脚本正常执行 的关系,核心是先明确「线程是进程内的执行单元」(一个进程至少包含一个线程,即主线程),再通过「是否默认创建」「是否阻塞主线程退出」两个维度区分,结合 Python threading 模块实例说明更易理解。
一、核心概念定义(附示例+特点)
1. 脚本的正常执行
定义
Python 解释器启动后,按顺序执行脚本顶层代码(不在函数/类/线程中的代码)、调用函数、处理逻辑 的过程,本质是「主线程的默认执行流程」。
特点
- 不需要显式创建线程,解释器自动触发;
- 所有逻辑都在「主线程」的内存空间中执行;
- 若未手动创建其他线程,脚本执行完顶层代码后直接退出。
示例(脚本正常执行=主线程执行流程)
python
# 以下所有代码都属于「脚本的正常执行」,运行在主线程中
import threading
import time
print("脚本开始执行(主线程)") # 顶层代码,主线程执行
def func():
print("函数调用(仍在主线程)")
func() # 函数执行,仍在主线程中
print("脚本执行结束(主线程退出)")
2. 主线程(Main Thread)
定义
Python 脚本启动时 自动创建的默认线程,是所有其他线程的「父线程」,脚本的正常执行就是主线程的执行流程。
核心特点
- 进程启动时自动创建,PID 与所属进程一致;
- 是子线程的创建者(所有手动创建的线程,父线程都是主线程);
- 主线程会 等待所有非守护子线程执行完毕后,才会退出(守护子线程除外);
- 主线程退出 → 整个进程退出(所有线程都会终止)。
如何验证主线程
python
import threading
# 获取当前线程对象,判断是否为主线程
current_thread = threading.current_thread()
print("当前线程名称:", current_thread.name) # 输出:MainThread(主线程默认名称)
print("是否为主线程:", current_thread.is_main_thread()) # 输出:True
3. 子线程(Child Thread)
定义
通过 threading.Thread 显式创建的线程,依附于主线程存在,是主线程的「子线程」,用于实现「并发执行」(比如同时处理数据写入、日志打印等)。
分类(按是否阻塞主线程退出)
子线程分为「非守护子线程」(默认)和「守护子线程」(显式设置 daemon=True),核心区别在「是否阻止主线程退出」。
特点
- 必须显式调用
threading.Thread创建,调用start()后才启动; - 父线程是主线程,主线程退出时:
- 非守护子线程:继续执行,主线程等待它完成后再退出;
- 守护子线程:立即终止,不阻塞主线程退出;
- 子线程的执行流程独立于主线程(主线程执行自己的逻辑,子线程执行
target函数逻辑)。
示例(创建非守护子线程)
python
import threading
import time
def child_task():
print("子线程启动(非守护)")
time.sleep(3) # 子线程执行耗时操作
print("子线程结束")
# 创建子线程(默认非守护)
child_thread = threading.Thread(target=child_task, name="ChildThread-1")
child_thread.start()
print("主线程执行到末尾(等待非守护子线程)")
# 主线程会阻塞在这里,直到 child_thread 执行完毕才退出
运行结果:
子线程启动(非守护)
主线程执行到末尾(等待非守护子线程)
子线程结束 # 3秒后输出,主线程才退出
4. 守护线程(Daemon Thread)
定义
显式设置 daemon=True 的子线程,是「后台服务型线程」,用于处理不影响核心逻辑的辅助任务(如日志收集、数据监控)。
核心特点
- 必须在
start()前设置daemon=True; - 主线程退出时,守护线程 立即终止(不管是否执行完);
- 不能创建自己的子线程(部分 Python 版本限制);
- 不阻塞主线程退出(主线程执行完自己的逻辑就退出,不管守护线程)。
示例(创建守护线程)
python
import threading
import time
def daemon_task():
print("守护线程启动")
while True: # 无限循环(本应长期运行)
print("守护线程运行中...")
time.sleep(1)
# 创建守护线程
daemon_thread = threading.Thread(target=daemon_task, name="DaemonThread-1")
daemon_thread.daemon = True # 关键:设置为守护线程
daemon_thread.start()
print("主线程执行到末尾(不等待守护线程)")
time.sleep(2) # 主线程延迟2秒
print("主线程退出")
运行结果:
守护线程启动
守护线程运行中...
主线程执行到末尾(不等待守护线程)
守护线程运行中...
主线程退出 # 主线程退出,守护线程立即终止(不再输出"守护线程运行中")
二、脚本启动的线程与这些线程的关系
脚本启动后,线程的创建和层级关系如下:
1. 启动流程
脚本启动 → 解释器创建「主线程」→ 主线程执行「脚本的正常执行流程」
↓
(若用户显式创建线程)
主线程创建「子线程」(非守护/守护)→ 子线程与主线程并发执行
↓
主线程执行完自身逻辑:
- 等待所有「非守护子线程」完成 → 主线程退出 → 进程退出(所有线程终止);
- 直接终止所有「守护子线程」→ 主线程退出 → 进程退出。
2. 核心关系总结
| 线程类型 | 创建方式 | 父线程 | 与主线程的关系 | 脚本退出时的表现 |
|---|---|---|---|---|
| 主线程 | 脚本启动时自动创建 | 无(进程默认) | 脚本的正常执行就是主线程的执行流程 | 等待非守护子线程后,进程退出 |
| 非守护子线程 | threading.Thread() |
主线程 | 与主线程并发,阻塞主线程退出 | 执行完自身逻辑后终止 |
| 守护子线程 | thread.daemon=True |
主线程 | 与主线程并发,不阻塞主线程退出 | 主线程退出时,立即终止 |
3. 关系图(直观理解)
Python 进程(PID=xxx)
└─ 主线程(MainThread,自动创建)
├─ 执行脚本顶层代码、函数调用(脚本的正常执行)
├─ 子线程1(非守护,显式创建)→ 并发执行,阻塞主线程退出
├─ 子线程2(守护,显式创建)→ 并发执行,不阻塞主线程退出
└─ 子线程3(非守护,显式创建)→ 并发执行,阻塞主线程退出
三、常见混淆点澄清
-
主线程 vs 脚本的正常执行 :
两者强绑定------脚本的正常执行是「主线程的执行内容」,主线程是「执行这个内容的载体」,没有主线程就没有脚本的正常执行。
-
子线程 vs 守护线程 :
守护线程是「子线程的子集」(子线程包含非守护和守护两种),不是独立于子线程的类型。
-
线程 vs 进程(呼应之前的进程知识):
- 线程是进程内的执行单元,共享进程内存;进程是独立的资源分配单元(如 PID、内存空间);
- 主线程是进程的默认线程,一个进程至少有一个主线程;子线程是进程内的额外线程,多个线程并发执行。
-
守护线程 vs 守护进程:
- 守护线程:进程内的后台线程,主线程退出则终止,依赖进程存在;
- 守护进程:独立的系统服务(PPID=1),脱离终端,不依赖父进程,两者完全不同!
四、实操建议(结合你的数据打包场景)
你的 ImageLMDBPacker 用了线程实现异步写入,可对应调整:
- 若写入逻辑必须完成(不能中途终止):用「非守护子线程」(默认),主线程需调用
thread.join()等待写入完成; - 若写入是辅助任务(允许中途停止):用「守护线程」,但需注意主线程退出时会丢失未写入的数据;
- 避免在子线程中使用依赖主线程的资源(如
signal模块,之前的报错就是子线程用了主线程专属的signal)。
并行读--cpu并行处理----并行写,那个地方是卡点,就去使用多线程与多进程或者其它的取优化卡点,与其他的无关
Python并发模型选择:多线程、多进程与异步的实战决策指南
现代计算机架构(多核 CPU、高速 SSD/NVMe、大内存、高带宽 I/O)确实原生支持并行读、并行计算、并行写。但 Python 由于 GIL(全局解释器锁)的存在,使得"如何选择并发模型"变得尤为关键。
下面从 硬件能力 + Python 特性 + 任务类型 三个维度,系统分析:在"并行读 → 并行处理 → 并行写"流水线中,何时用多线程?何时用多进程?何时用异步?
一、先看任务本质:I/O 密集 vs CPU 密集
| 类型 | 特征 | 典型场景 |
|---|---|---|
| I/O 密集 | 大量等待磁盘/网络/数据库响应 | 文件读写、HTTP 请求、DB 查询 |
| CPU 密集 | 持续占用 CPU 进行计算 | 图像处理、加密、数值模拟、正则匹配 |
| ✅ 核心原则: |
-
I/O 密集 → 多线程 或 异步(GIL 在 I/O 时释放)
-
CPU 密集 → 多进程(绕过 GIL,利用多核)
二、分阶段优化策略(结合现代硬件)
阶段 1️⃣:并行读(Parallel Reading)
硬件支持:NVMe SSD 可同时处理数千 I/O 队列;多文件可真正并行读取。
任务类型:I/O 密集
推荐方案:
- ✅ 多线程(ThreadPoolExecutor):简单、高效、轻量。
python
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor() as pool:
data = list(pool.map(read_file, paths))
- ✅ 异步 I/O(asyncio + aiofiles):适合大量小文件或网络 I/O,内存更省。
python
async def read_async(path):
async with aiofiles.open(path) as f:
return await f.read()
- ❌ 不要用多进程:进程启动开销大,I/O 不需要绕 GIL。
📌 结论:优先多线程;若需极致高并发(如万级连接),选异步。
阶段 2️⃣:并行处理(Parallel Processing)
硬件支持:多核 CPU(8核、16核甚至更多)可真正并行计算。
任务类型:CPU 密集
推荐方案:
- ✅ 多进程(ProcessPoolExecutor):唯一能绕过 GIL 的方式。
python
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as pool:
results = list(pool.map(cpu_task, data))
⚠️ C 扩展(如 NumPy、Pandas、OpenCV)例外:它们内部释放 GIL,多线程也能并行!
例如:np.dot()、pd.merge() 等底层用 C/C++/BLAS 实现,可安全用多线程。
❌ 纯 Python 的 CPU 计算 + 多线程 = 无效(GIL 串行化)。
📌 结论:纯 Python 计算 → 多进程;用 NumPy/Pandas → 多线程可能更优。
阶段 3️⃣:并行写(Parallel Writing)
硬件支持:SSD 支持高并发写入(尤其多文件)。
任务类型:I/O 密集
推荐方案:
- ✅ 多线程写多个独立文件:安全且高效。
python
with ThreadPoolExecutor() as pool:
pool.map(write_file, zip(paths, results))
⚠️ 写同一文件?→ 单线程批量写!
多线程写同一文件需加锁,反而降低性能;更佳做法:缓冲结果 → 一次性写入。
✅ 异步写:适合网络写(如发 HTTP 响应、写 Kafka)。
📌 结论:写目标独立 → 多线程;写共享资源 → 合并后单写。
三、对比三种模型的适用场景
| 模型 | 优势 | 劣势 | 最佳场景 |
|---|---|---|---|
| 多线程 | 轻量、启动快、适合 I/O | 受 GIL 限制,不能并行 CPU 计算 | 文件读写、网络请求、DB 操作 |
| 多进程 | 真正并行 CPU、绕过 GIL | 内存开销大、IPC 慢 | 纯 Python CPU 密集任务 |
| 异步(async) | 单线程高并发、内存占用极低 | 编程复杂、需全链路 async 支持 | 高并发网络服务、大量 I/O 等待 |
| 💡 现代趋势: |
-
Web 服务 → 异步(FastAPI/Starlette)
-
数据处理 → 多进程(Dask/Ray) + 多线程(I/O)混合
-
简单脚本 → ThreadPool / ProcessPool 足够
四、终极建议:混合使用(Hybrid Approach)
现代高性能 Python 程序往往是 "多线程 + 多进程"混合架构:
text
[多线程] 并行读文件
↓
[多进程] 并行 CPU 处理
↓
[多线程] 并行写多个输出文件
✅ 示例框架:
-
用 ThreadPoolExecutor 读/写;
-
用 ProcessPoolExecutor 处理;
-
用 queue.Queue 或 multiprocessing.Queue 桥接。
✅ 总结:一句话决策指南
-
卡在 I/O(读/写/网络)? → 用多线程或异步
-
卡在 CPU(纯 Python 计算)? → 用多进程
-
用 NumPy/Pandas? → 多线程可能已足够(因底层释放 GIL)
-
不确定? → 先测量(cProfile + htop),再优化瓶颈点!
这才是充分利用现代硬件 + 尊重 Python 特性的理性并发之道。
(注:文档部分内容可能由 AI 生成)