Python 多任务编程入门:进程的创建、同步与进程池使用

文章目录

在Python开发中,当我们需要同时处理多个任务时------比如一边爬取网页数据,一边将已爬取的数据写入数据库,单线程的串行执行就会显得效率低下。这时候, 多任务编程 就成了提升程序性能的关键手段。

进程作为操作系统资源分配的最小单位,是实现多任务的核心方式之一。本文将从多任务的基本概念出发,系统梳理Python中进程的创建、管理、同步控制以及进程池的使用技巧,帮你快速掌握进程级多任务编程的核心知识。

一、多任务的基础认知

在深入学习进程之前,我们首先要搞清楚:什么是多任务?它有哪些表现形式?

1. 多任务的定义

多任务是指在同一时间内执行多个任务。在Python中,实现多任务的方式主要有三种:

  • 进程:操作系统资源分配的最小单位,每个进程拥有独立的内存空间。

  • 线程:CPU调度的最小单位,线程共享所属进程的资源。

  • 协程:用户态的轻量级线程,由程序自身控制调度,开销最小。

2. 多任务的两种表现形式

多任务的执行看似是"同时进行",但实际上分为并发并行两种核心模式,二者的底层逻辑有本质区别:

表现形式 核心逻辑 硬件要求 示例
并发 多个任务交替抢占CPU资源,在宏观上表现为"同时执行",微观上是串行执行 可在单CPU核心下实现 单核心CPU上,多个应用程序交替运行
并行 多个任务真正同时执行,每个任务占用独立的CPU核心 必须依赖多CPU核心 多核心CPU上,多个任务在不同核心上同时运行
简单来说:并发是"交替执行",并行是"同时执行"

二、进程:资源分配的最小单位

1. 进程的基本概念

一个正在运行的程序就是一个进程。比如我们打开Python解释器运行脚本,就会创建一个Python进程;在脚本中新建任务时,又会产生子进程。

进程的核心特性是独立性:每个进程都拥有独立的内存空间、文件描述符等系统资源,进程之间默认不共享资源。

2. 主进程与子进程

  • 主进程:程序启动时默认创建的进程,是程序的入口和核心,负责管理子进程。

  • 子进程:程序运行过程中,通过进程模块新建的进程,用于执行特定的子任务。

重要注意点 :正常情况下,主进程的优先级高于子进程,但子进程的执行时机并非绝对可控。因为进程的调度权由操作系统内核决定,内核会根据时间片、进程优先级等因素分配CPU资源,可能出现子进程启动后立即执行的情况。

三、Python中创建多进程的核心步骤

Python的multiprocessing模块是实现多进程编程的核心工具,通过它可以轻松创建和管理子进程。

1. 多进程实现的三大步骤

步骤1:导入进程包
python 复制代码
import multiprocessing
步骤2:创建子进程对象

通过multiprocessing.Process类创建子进程对象,核心参数如下:

python 复制代码
进程对象 = multiprocessing.Process(
    group=None,  # 进程组,目前仅支持None
    target=None, # 要执行的目标任务函数名(**不能带括号**,否则会直接调用函数而非交给子进程执行)
    name=None,   # 进程名,可选,用于标识进程
    args=(),     # 传递给目标函数的位置参数,**必须是元组类型**(单个参数需加逗号,如(10,))
    kwargs={}    # 传递给目标函数的关键字参数,**必须是字典类型**,如{'a': 1, 'b': 2}
)

关键提醒target参数必须是函数名本身,不能带括号。如果写成target=func(),会直接在主进程中执行函数,而非交给子进程执行。

步骤3:启动子进程

通过start()方法启动子进程,此时操作系统会为子进程分配资源并执行目标函数。

python 复制代码
进程对象.start()

2. 基础示例:多进程执行任务

python 复制代码
import multiprocessing
import time

# 定义要执行的任务函数
def task(name, delay):
    for i in range(3):
        print(f"任务{name}执行中:{i}")
        time.sleep(delay)

if __name__ == '__main__':
    # 创建子进程1:传递位置参数
    p1 = multiprocessing.Process(target=task, args=("A", 1))
    # 创建子进程2:传递关键字参数
    p2 = multiprocessing.Process(target=task, kwargs={"name": "B", "delay": 0.5})

    # 启动子进程
    p1.start()
    p2.start()

运行上述代码,你会看到两个任务交替执行,这就是多进程实现的多任务效果。

四、多进程的核心管理技巧

创建子进程后,我们还需要掌握进程的同步、阻塞、编号获取等核心管理技巧,才能更好地控制多任务的执行流程。

1. 进程的阻塞与同步:join()方法

join()方法的作用是阻塞当前进程 ,直到调用join()的子进程执行完毕后,再继续执行后续代码。这是实现进程同步的关键方法。

基础用法:等待单个子进程完成
python 复制代码
p1.start()
p1.join()  # 主进程阻塞,直到p1执行完毕
print("p1执行完毕,开始执行后续代码")
进阶用法:确保子进程按顺序执行

如果需要让多个子进程按顺序执行 (而非交替执行),可以在创建每个子进程后,立即调用start()join()方法。

python 复制代码
p1.start()
p1.join()  # 等待p1完成
p2.start()
p2.join()  # 等待p2完成

2. 进程的同步控制:进程锁(Lock)

当多个进程需要访问共享资源 (如同一文件、同一设备)时,可能会出现资源竞争问题,导致数据错乱。此时需要使用进程锁来保证同一时间只有一个进程能访问共享资源。

进程锁的核心特性
  • 互斥性:同一时间,只有一个进程能成功获取锁,其他进程尝试获取锁会进入阻塞状态。

  • 阻塞性 :当锁被其他进程持有时,lock.acquire()会让当前进程暂停执行,直到锁被释放。

进程锁的使用步骤
  1. 创建锁对象
    m1_lock = multiprocessing.Lock()

  2. 传递锁对象 :通过args参数将锁传递给子进程(不能使用全局变量共享,因为进程间资源独立)。

  3. 获取锁与释放锁

    • lock.acquire():获取锁(锁上门),如果锁已被占用,则阻塞。

    • lock.release():释放锁(打开门),唤醒等待锁的进程。

进程锁的使用示例
python 复制代码
import multiprocessing
import time

def task(lock, name):
    lock.acquire()  # 获取锁
    try:
        for i in range(2):
            print(f"任务{name}独占资源执行:{i}")
            time.sleep(0.5)
    finally:
        lock.release()  # 释放锁,确保即使出现异常也能释放锁

if __name__ == '__main__':
    lock = multiprocessing.Lock()
    p1 = multiprocessing.Process(target=task, args=(lock, "A"))
    p2 = multiprocessing.Process(target=task, args=(lock, "B"))

    p1.start()
    p2.start()

重要提醒 :一个进程锁只能被锁定一次 ,如果同一进程尝试两次调用acquire(),会导致进程死锁(卡住),只有释放锁后才能再次获取。

3. 进程编号的获取:pid与ppid

为了方便管理进程(如查看进程状态、终止进程),我们可以通过os模块获取进程的编号。

  • os.getpid():获取当前进程的编号(PID)。

  • os.getppid():获取当前进程的父进程的编号(PPID)。

示例:获取进程编号
python 复制代码
import multiprocessing
import os

def task():
    print(f"子进程PID:{os.getpid()}")
    print(f"子进程的父进程PPID:{os.getppid()}")

if __name__ == '__main__':
    print(f"主进程PID:{os.getpid()}")
    p = multiprocessing.Process(target=task)
    p.start()

4. 必知的魔术方法:if name == 'main'

在Windows系统中,使用multiprocessing模块创建子进程时,if __name__ == '__main__': 必须将进程创建的代码放在语句块中,否则会导致程序无限递归创建子进程,最终报错。

核心作用
  1. 区分主进程与子进程 :Windows系统在创建子进程时,会重新导入主模块,__name__变量在主进程中为'__main__',在子进程中为模块名,从而避免重复执行进程创建代码。

  2. 支持模块导入:当模块被其他脚本导入时,不会执行进程创建的代码,保证模块的可复用性。

正确写法示例
python 复制代码
import multiprocessing

def task():
    print("子进程执行中")

if __name__ == '__main__':
    # 仅在主进程中创建子进程
    p = multiprocessing.Process(target=task)
    p.start()

补充 :在Linux/Mac系统中,由于使用fork机制创建子进程,无需强制使用该语句块,但为了代码的跨平台兼容性,强烈推荐始终使用

五、进程池:高效管理大量子进程

当需要执行大量短时间任务 时,频繁创建和销毁子进程会带来巨大的性能开销。此时,进程池(Pool)是更优的选择。

1. 进程池的核心思想

进程池会预先创建一批固定数量的子进程,放入"池子"中复用。当有新任务到来时,直接分配给池中的空闲进程执行;任务完成后,进程不会被销毁,而是等待下一个任务。这样可以有效避免频繁创建/销毁进程的性能损耗。

2. 进程池的适用场景

  • 任务数量多、单个任务执行时间短。

  • 任务之间相互独立,可并行执行。

  • 需要控制并发进程数量,防止CPU资源耗尽。

3. 进程池的核心类与方法

进程池的核心类是multiprocessing.Pool,常用方法如下:

  • apply_async(func, args=(), kwargs={})异步提交任务(推荐),不会阻塞主进程,任务完成后可通过返回值获取结果。

  • apply(func, args=(), kwargs={})同步提交任务,会阻塞主进程,直到任务执行完毕,不推荐用于多任务场景。

  • map(func, iterable)批量提交简单任务,将可迭代对象中的每个元素作为参数传递给函数。

  • close():关闭进程池,不再接收新任务

  • join():阻塞主进程,等待进程池中所有任务执行完毕。

4. 进程池的使用示例

python 复制代码
import multiprocessing
import os
import time

def task(num):
    time.sleep(1)
    return f"任务{num}执行完毕,进程PID:{os.getpid()}"

if __name__ == '__main__':
    # 创建进程池,设置最大进程数为4(建议等于CPU核心数)
    pool = multiprocessing.Pool(processes=4)

    # 异步提交10个任务
    results = []
    for i in range(10):
        res = pool.apply_async(task, args=(i,))
        results.append(res)

    # 关闭进程池,不再接收新任务
    pool.close()
    # 等待所有任务执行完毕
    pool.join()

    # 获取所有任务的结果
    for res in results:
        print(res.get())

六、多进程编程的核心注意事项

1. 进程间不共享全局资源

每个进程都有独立的内存空间,子进程会拷贝主进程的资源,但修改子进程中的全局变量不会影响主进程,反之亦然。

如果需要实现进程间的通信,可以使用multiprocessing模块提供的队列(Queue)、**管道(Pipe)**等工具。

2. 主进程与子进程的结束顺序

默认情况下,主进程会等待所有子进程执行完毕后才会结束。如果需要让主进程结束时,子进程也立即终止,可以采用以下两种方案:

方案1:设置守护进程

通过将子进程的daemon属性设置为True,使子进程成为守护进程。主进程结束时,守护进程会被立即销毁,不再执行剩余代码。

python 复制代码
p = multiprocessing.Process(target=task)
p.daemon = True  # 设置为守护进程
p.start()
方案2:手动终止子进程

在主进程结束前,调用子进程的terminate()方法,手动终止子进程的执行。

python 复制代码
p = multiprocessing.Process(target=task)
p.start()

# 主进程执行其他任务
time.sleep(2)

# 终止子进程
p.terminate()
print("主进程执行完毕,子进程已终止")

七、总结

进程作为Python多任务编程的重要方式,适用于CPU密集型任务(如数据计算、图像处理),通过充分利用多核心CPU资源,可以显著提升程序的执行效率。

本文梳理了进程的基本概念、创建步骤、管理技巧以及进程池的使用方法,核心要点如下:

  1. 多任务分为并发和并行两种表现形式,进程是实现多任务的核心方式之一。

  2. 使用multiprocessing.Process创建子进程,需注意target参数的写法和if __name__ == '__main__'的必要性。

  3. 通过join()实现进程同步,通过Lock解决资源竞争问题。

  4. 进程池适用于大量短时间任务,可有效避免频繁创建/销毁进程的性能开销。

  5. 进程间默认不共享资源,主进程与子进程的结束顺序可通过守护进程daemonterminate()方法控制。

相关推荐
不要em0啦11 小时前
从0开始学python:简单的练习题4
开发语言·python
我想吃余11 小时前
【C++篇】C++11:线程库
开发语言·c++
AI Echoes11 小时前
LangChain中的工具与工具包
人工智能·python·langchain·prompt·agent
AI题库11 小时前
PostgreSQL 18 从新手到大师:实战指南 - 2.6 PostgreSQL管理工具
数据库·postgresql
henreash11 小时前
C#调用F#的MailboxProcessor
开发语言·c#
CSDN_RTKLIB11 小时前
【静态初始化与动态初始化】术语对比
开发语言·c++
浔川python社11 小时前
浔川社团 2026 更新程序安排
python
彼岸花开了吗11 小时前
构建AI智能体:七十二、交叉验证:从模型评估的基石到大模型时代的演进
人工智能·python·llm
赵长辉11 小时前
AGI-rag学习: 实现了一个混合搜索系统,结合了 BM25 和 密集向量检索两种方法 【20251016课复习】
python·学习·agi