注意点:多线程与多进程与在并行读-->并行cpu或者GPU处理--->并行写 的架构中,如何选择

哪些是子进程,哪些是主进程,哪些是守护进程,哪些是脚本的正常执行,脚本运行启动的进程与这些进程有什么关系

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 脚本可以实现这类守护进程,但需手动处理:

  1. 第一次 fork → 父进程退出,子进程继续;
  2. 调用 os.setsid() 创建新会话,脱离控制终端;
  3. 第二次 fork → 确保不能重新打开终端;
  4. 重定向 stdin/stdout/stderr 到 /dev/null;
  5. 改变工作目录(如 /);
  6. 设置 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)

五、最佳实践建议

  1. 需要后台服务? → 使用 systemd 管理 Python 脚本,或用 python-daemon 库实现守护。
  2. 需要并行任务? → 优先用 multiprocessing(CPU 密集)或 concurrent.futures。
  3. 调用外部命令? → 用 subprocess.run()(推荐)或 Popen(需精细控制)。
  4. 避免僵尸进程 → 对 subprocess 调用 .wait() 或使用 with Popen(...) as p:
  5. 不要混淆 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(非守护,显式创建)→ 并发执行,阻塞主线程退出

三、常见混淆点澄清

  1. 主线程 vs 脚本的正常执行

    两者强绑定------脚本的正常执行是「主线程的执行内容」,主线程是「执行这个内容的载体」,没有主线程就没有脚本的正常执行。

  2. 子线程 vs 守护线程

    守护线程是「子线程的子集」(子线程包含非守护和守护两种),不是独立于子线程的类型。

  3. 线程 vs 进程(呼应之前的进程知识)

    • 线程是进程内的执行单元,共享进程内存;进程是独立的资源分配单元(如 PID、内存空间);
    • 主线程是进程的默认线程,一个进程至少有一个主线程;子线程是进程内的额外线程,多个线程并发执行。
  4. 守护线程 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 生成)

相关推荐
木棉知行者17 分钟前
(二)Python基本语句
开发语言·python
傻啦嘿哟18 分钟前
2026版基于Python的旅游景点推荐系统:技术解析与实现路径
开发语言·python
小陈phd20 分钟前
RAG从入门到精通(十四)——评估技术
人工智能·python
一晌小贪欢22 分钟前
Python-12 Python生成器与yield:惰性求值的艺术
开发语言·python·python基础·python3·python小白·python生成器
vortex524 分钟前
基于 Apache 规则拦截目录扫描器请求:实测与配置指南
linux·网络安全·apache
简单的话*26 分钟前
Logback 日志按月归档并保留 180 天,超期自动清理的配置实践
java·前端·python
蓝眸少年CY28 分钟前
Python科学计算 Numpy库
开发语言·python·numpy
摸鱼仙人~28 分钟前
Ubuntu系统安装VMware Tools 完整流程(解压→安装)
linux·运维·ubuntu
真好啊又活了一天28 分钟前
VMware安装界面闪退,且没有任何提示 原因与解决方法
网络