目录

一、多任务基础:并发与并行

二、进程:操作系统资源分配的最小单位

2.1、进程的核心概念

2.2、多进程的实现

2.3、进程的关键知识点

进程编号

进程间不共享全局变量

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

进程同步

场景1:Lock锁机制

场景2:Event事件

2.4、进程的优缺点

三、线程:CPU 调度的最小单位

3.1、线程的核心概念

3.2、多线程的实现(threading 模块)

3.3、线程的关键知识点

3.4、线程的优缺点


        在 Python 开发中,面对大量 I/O 操作、高并发请求等场景,单任务执行的效率短板会被无限放大。而多任务编程能让程序在同一时间处理多个操作,充分利用 CPU 资源,是提升程序执行效率的核心手段。Python 中实现多任务的三大核心方式为进程线程协程,三者各有特性和适用场景。

一、多任务基础:并发与并行

        想要理解进程、线程和协程,首先要明确多任务的两种核心表现形式:并发并行,这是多任务编程的底层逻辑。

  1. 并发:在一段时间内交替执行多个任务,适用于单核 CPU。操作系统会让各个任务轮流执行极短的时间,由于 CPU 执行速度极快,从表面看就像任务在同时运行,本质是任务数大于 CPU 核心数
  2. 并行:在一段时间内真正同时执行多个任务,适用于多核 CPU。操作系统会为每个 CPU 内核分配独立的任务,多个任务在不同内核上同时运行,本质是任务数小于或等于 CPU 核心数

        简单来说,并发是 “交替做”,并行是 “同时做”,而进程、线程、协程的设计,正是为了在不同场景下实现高效的并发或并行。

二、进程:操作系统资源分配的最小单位

2.1、进程的核心概念

        进程(Process)是操作系统进行资源分配和调度的基本单位,一个正在运行的程序就是一个进程(比如运行中的 QQ、Python 解释器)。一个程序运行后至少会创建一个主进程,若需要实现多任务,可由主进程创建多个子进程,每个子进程独立执行一个任务。

进程的核心特点:每个进程拥有独立的内存空间和系统资源,进程之间相互独立,互不干扰。

2.2、多进程的实现

        Python 中通过multiprocessing模块实现多进程,核心步骤为导入模块、创建进程对象、启动进程,同时支持为进程任务传递参数(元组args/ 字典kwarg s)。

# 导入 多进程模块
import multiprocessing
import time

# 定义任务函数
def music(num, name):
    for i in range(num):
        print(f"听{name}...{i+1}")
        time.sleep(0.2)

def coding(count):
    for i in range(count):
        print(f"敲代码...{i+1}")
        time.sleep(0.2)

if __name__ == '__main__':
    # 创建进程对象,传递参数 target:函数名
    p1 = multiprocessing.Process(target=music, args=(3, "忘情水"))
    p2 = multiprocessing.Process(target=coding, kwargs={"count": 3})
    # 启动进程
    p1.start()
    p2.start()
    # 等待子进程执行完毕后,主进程再继续
    # join:让主进程阻塞,让子进程执行完毕后再执行其它进程
    p1.join()
    p2.join()
    print("主进程结束")

        运行这段多进程代码时,每次输出的顺序可能不一致(比如先打印 “听忘情水” 还是先打印 “敲代码” 的顺序不固定),核心原因是:操作系统对进程的调度是随机的、抢占式的,而非按代码编写顺序执行。

2.3、进程的关键知识点

进程编号

        每个进程有唯一的 PID(进程 ID),可通过os.getpid( )获取当前进程 ID,os.getppid( )获取父进程 ID,用于进程管理。

import multiprocessing
import os
import time

def child_task():
    # 子进程中获取自身PID和父进程PID
    print(f"子进程 - 自身PID: {os.getpid()}, 父进程PID: {os.getppid()}")
    time.sleep(1)  # 防止子进程过快结束

if __name__ == '__main__':
    # 主进程PID
    main_pid = os.getpid()
    print(f"主进程 - 自身PID: {main_pid}")
    
    # 创建2个子进程
    p1 = multiprocessing.Process(target=child_task)
    p2 = multiprocessing.Process(target=child_task)
    
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    
    print("所有子进程执行完毕")

进程间不共享全局变量

        创建子进程时,会拷贝主进程的所有资源,子进程操作的是自己的资源副本,而非主进程的全局变量,因此进程间无法直接共享数据。

import multiprocessing
import time

# 定义全局变量
global_num = 0

def modify_num():
    global global_num  # 声明使用全局变量(实际是子进程的副本)
    global_num += 10
    print(f"子进程内修改后:global_num = {global_num}")

if __name__ == '__main__':
    print(f"主进程初始:global_num = {global_num}")
    
    # 创建子进程并执行修改操作
    p = multiprocessing.Process(target=modify_num)
    p.start()
    p.join()
    
    # 主进程查看全局变量(未被修改)
    print(f"主进程最终:global_num = {global_num}")

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

        主进程默认不会等待子进程执行完毕,若需主进程结束时子进程也立即停止,有两种方案:

  • 设置守护进程:p1.daemon = True,主进程结束,子进程直接销毁;
  • 主动销毁子进程:p1.terminate( ),强制终止子进程。
import multiprocessing
import time

def long_task(name):
    print(f"子进程{name}启动,开始执行长时间任务...")
    i = 0
    while True:  # 无限循环,模拟长时间任务
        i += 1
        print(f"子进程{name}运行中...{i}")
        time.sleep(0.5)

if __name__ == '__main__':
    # ========== 案例1:设置守护进程 ==========
    print("===== 测试守护进程 =====")
    p_daemon = multiprocessing.Process(target=long_task, args=("守护进程",))
    p_daemon.daemon = True  # 设置为守护进程
    p_daemon.start()
    time.sleep(2)  # 主进程等待2秒后结束
    print("主进程结束,守护进程被强制销毁")
    
    # ========== 案例2:主动终止子进程 ==========
    print("\n===== 测试terminate() =====")
    p_terminate = multiprocessing.Process(target=long_task, args=("普通进程",))
    p_terminate.start()
    time.sleep(2)  # 让子进程运行2秒
    p_terminate.terminate()  # 主动终止子进程
    p_terminate.join()  # 等待子进程彻底终止
    print("主进程主动终止子进程,子进程停止运行")

案例1:

案例2:

关键说明:

  • 守护进程:主进程执行 2 秒后结束,守护进程被直接销毁,不会继续运行;
  • terminate():主进程主动调用该方法,强制终止子进程,即使子进程是无限循环也会停止;
  • 注意:terminate()后建议调用join(),确保子进程彻底释放资源。

进程同步

        若多个进程需要操作共享资源(如文件),需使用multiprocessing.Lock实现锁机制,防止数据竞争;若需严格控制进程执行顺序,可使用multiprocessing.Event实现进程间信号传递(规定执行顺序)。

场景1:Lock锁机制

核心逻辑

        多个进程同时写入同一个文件时,会出现 “数据错乱”,通过Lock加锁,保证同一时间只有一个进程写入。

import multiprocessing
import time

def write_file(num, lock):
    # 加锁:确保同一时间只有一个进程执行写入操作
    lock.acquire()
    try:
        with open("test.txt", "a", encoding="utf-8") as f:
            f.write(f"进程{num}写入:{time.ctime()}\n")
            print(f"进程{num}写入完成")
        time.sleep(0.5)  # 模拟写入耗时
    finally:
        # 必须释放锁,否则其他进程会一直等待
        lock.release()

if __name__ == '__main__':
    # 初始化锁
    lock = multiprocessing.Lock()
    
    # 清空文件(方便测试)
    with open("test.txt", "w", encoding="utf-8") as f:
        f.write("")
    
    # 创建5个进程同时写入文件
    processes = []
    for i in range(5):
        p = multiprocessing.Process(target=write_file, args=(i, lock))
        processes.append(p)
        p.start()
    
    for p in processes:
        p.join()
    
    print("所有进程写入完成,查看test.txt内容:")
    with open("test.txt", "r", encoding="utf-8") as f:
        print(f.read())

关键说明

  • 不加锁时,多个进程的写入内容会 “混在一起”(比如一行内容被截断);
  • 加锁后,写入操作串行执行,数据完整无错乱;
  • 建议用try...finally包裹锁操作,确保即使发生异常也能释放锁。
场景2:Event事件

核心逻辑

        通过Event的set()(设置信号)和wait()(等待信号),强制让进程 2 必须等进程 1 执行完后再运行。

import multiprocessing
import time

def process1(event):
    print("进程1开始执行任务...")
    time.sleep(2)  # 模拟任务耗时
    print("进程1任务完成,发送信号让进程2执行")
    event.set()  # 设置信号,通知等待的进程

def process2(event):
    print("进程2等待进程1完成...")
    event.wait()  # 等待信号,未收到信号则阻塞
    print("进程2开始执行任务...")
    time.sleep(1)
    print("进程2任务完成")

if __name__ == '__main__':
    # 初始化事件(初始状态为False)
    event = multiprocessing.Event()
    
    p1 = multiprocessing.Process(target=process1, args=(event,))
    p2 = multiprocessing.Process(target=process2, args=(event,))
    
    # 启动进程(先启动p2,验证p2会等待p1)
    p2.start()
    time.sleep(0.5)  # 确保p2先进入等待状态
    p1.start()
    
    p1.join()
    p2.join()
    print("所有进程执行完毕")

关键说明

  • 即使先启动 p2,p2 也会卡在event.wait( )处,直到 p1 调用event.set( )发送信号;
  • Event适合 “进程 A 必须等进程 B 完成某步操作后再执行” 的场景,比join()更灵活(join( )是等整个进程结束,Event可控制进程内的某一步)。

2.4、进程的优缺点

  1. 优点:可利用多核 CPU 实现并行执行,多个进程相互独立,一个进程崩溃不会影响其他进程;
  2. 缺点:创建进程的资源开销大(内存、CPU 资源),进程间通信需要借助管道、消息队列等 IPC 机制,实现复杂。

三、线程:CPU 调度的最小单位

3.1、线程的核心概念

        线程(Thread)是程序执行的最小单位,是进程的组成部分,一个进程至少包含一个主线程,也可创建多个子线程。

        进程仅负责分配资源,而线程才是真正执行程序的主体。同一进程的所有线程共享进程的内存空间和系统资源(如全局变量、文件句柄),就像一个 QQ 程序(进程)打开多个聊天窗口(线程),无需重复分配资源,因此线程的资源开销远低于进程。

3.2、多线程的实现(threading 模块)

        Python 中通过threading模块实现多线程,实现步骤与多进程高度相似,降低了学习成本,同样支持args/kwargs传参。

import threading
import time

# 定义任务函数
def music(num):
    for i in range(num):
        print("听音乐...")
        time.sleep(0.2)

def coding(count):
    for i in range(count):
        print("敲代码...")
        time.sleep(0.2)

if __name__ == '__main__':
    # 创建线程对象
    t1 = threading.Thread(target=music, args=(3,))
    t2 = threading.Thread(target=coding, kwargs={"count": 3})
    # 启动线程
    t1.start()
    t2.start()
    # 等待子线程执行完毕
    t1.join()
    t2.join()
    print("程序结束")

3.3、线程的关键知识点

  1. 线程的执行顺序:线程间的执行是无序的,由 CPU 调度随机决定,可通过threading.current_thread()获取当前线程信息,查看线程执行状态。
  2. 线程间共享全局变量:同一进程的所有线程共享全局变量,这是线程的优势,但也会带来数据竞争问题(多个线程同时修改全局变量),需通过threading.Lock加锁解决。
  3. 守护线程:线程无法像进程一样被主动销毁,若需主线程结束时子线程停止,只能设置守护线程:t1.daemon = True
  4. GIL 锁的限制:Python 中的全局解释器锁(GIL)导致同一时刻只有一个线程能执行 Python 字节码,因此多线程无法利用多核 CPU 实现并行,仅适用于 I/O 密集型任务(如网络请求、文件读写),而非 CPU 密集型任务。

3.4、线程的优缺点

  • 优点:创建线程的资源开销小、启动速度快,线程间共享资源,通信简单;
  • 缺点:受 GIL 锁限制,无法利用多核 CPU,线程间共享资源易引发数据竞争,需加锁同步,一个线程崩溃可能导致整个进程崩溃。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐