9.2 多进程入门


文章目录

  • 前言
  • [一、 进程与线程的概念](#一、 进程与线程的概念)
  • [二、 为什么需要多进程?](#二、 为什么需要多进程?)
  • [三、 Process 类:创建和管理进程](#三、 Process 类:创建和管理进程)
    • [3.1 创建单个进程](#3.1 创建单个进程)
    • [3.2 创建多个进程](#3.2 创建多个进程)
    • [3.3 Process 类的重要属性和方法](#3.3 Process 类的重要属性和方法)
  • [四、 进程池(Pool):高效管理多个进程](#四、 进程池(Pool):高效管理多个进程)
    • [4.1 使用进程池](#4.1 使用进程池)
    • [4.2 进程池的高级用法](#4.2 进程池的高级用法)
  • [五、 进程间通信(IPC)](#五、 进程间通信(IPC))
    • [5.1 管道(Pipe)](#5.1 管道(Pipe))
    • [5.2 队列(Queue)](#5.2 队列(Queue))
  • [六、 进程间同步](#六、 进程间同步)
  • 七、进程间共享状态
    • [7.1 共享内存(Value和Array)](#7.1 共享内存(Value和Array))
    • [7.2 服务进程(Manager)](#7.2 服务进程(Manager))
  • [八、 实际应用示例](#八、 实际应用示例)
    • [8.1 并行数据处理](#8.1 并行数据处理)
  • [九、 注意事项和最佳实践](#九、 注意事项和最佳实践)
    • [9.1 重要注意事项](#9.1 重要注意事项)
    • [9.2 最佳实践总结](#9.2 最佳实践总结)
  • 总结

前言

本文主要介绍进程与线程的概念、Process类、进程池、进程间通信以及应用示例等知识点。


一、 进程与线程的概念

进程:一个正在运行的应用程序实例,拥有独立的内存空间和系统资源。例如:启动一个音乐播放器应用程序就是一个进程。

线程:进程内部的最小执行单元,一个进程可以包含多个线程,它们共享进程的内存空间。例如:在音乐播放器中选择并播放一首歌曲就是一个线程任务。

二、 为什么需要多进程?

在多线程编程中我们了解到,由于 GIL(全局解释器锁) 的存在,CPython 解释器下的多线程在 CPU 密集型任务中无法实现真正的并行计算。为了充分利用多核 CPU 的优势,Python 提供了 multiprocessing 模块。

multiprocessing 模块的优势:

使用子进程代替线程,绕过 GIL 限制

每个进程有独立的 Python 解释器和内存空间

可实现真正的并行计算

API 与 threading 模块类似,学习成本低

三、 Process 类:创建和管理进程

3.1 创建单个进程

python 复制代码
python
from multiprocessing import Process
import os
import time

def task(name, duration):
    """子进程要执行的任务"""
    print(f'子进程 {name} 开始 (PID: {os.getpid()})')
    time.sleep(duration)
    print(f'子进程 {name} 结束 (PID: {os.getpid()})')

if __name__ == '__main__':
    # 主进程信息
    print(f'主进程 ID: {os.getpid()}')
    
    # 创建进程对象
    p = Process(target=task, args=('任务1', 2))
    
    # 启动进程
    p.start()
    print('主进程继续执行其他工作...')
    
    # 等待子进程结束
    p.join()
    print('子进程已结束,主进程继续')

3.2 创建多个进程

python 复制代码
python
from multiprocessing import Process
import os
import time

def worker(task_id, sleep_time):
    print(f'工作进程 {task_id} (PID: {os.getpid()}) 开始')
    time.sleep(sleep_time)
    print(f'工作进程 {task_id} (PID: {os.getpid()}) 完成')
    return f'任务{task_id}_结果'

if __name__ == '__main__':
    processes = []
    
    # 创建多个进程
    for i in range(5):
        p = Process(target=worker, args=(i, i+1))
        processes.append(p)
        p.start()
        print(f'已启动进程 {i}')
    
    # 等待所有进程完成
    for i, p in enumerate(processes):
        p.join()
        print(f'进程 {i} 已完成等待')
    
    print('所有进程执行完毕')

3.3 Process 类的重要属性和方法

python 复制代码
python
from multiprocessing import Process
import time
import os

def long_running_task():
    print(f'进程 {os.getpid()} 开始运行')
    time.sleep(5)
    print(f'进程 {os.getpid()} 运行结束')

if __name__ == '__main__':
    # 创建进程
    p = Process(target=long_running_task, name='工作进程')
    
    # 进程属性
    print(f'进程名称: {p.name}')
    print(f'是否守护进程: {p.daemon}')
    
    # 启动前
    print(f'启动前是否存活: {p.is_alive()}')
    
    # 启动进程
    p.start()
    print(f'启动后是否存活: {p.is_alive()}')
    print(f'进程ID: {p.pid}')
    
    # 等待一段时间后检查
    time.sleep(1)
    print(f'1秒后是否存活: {p.is_alive()}')
    
    # 设置超时等待
    p.join(timeout=2)
    print(f'等待2秒后是否存活: {p.is_alive()}')
    
    # 强制终止进程(谨慎使用)
    if p.is_alive():
        p.terminate()
        print('进程已被终止')
    
    # 获取退出代码
    print(f'退出代码: {p.exitcode}')
    
    # 清理资源
    p.close()

四、 进程池(Pool):高效管理多个进程

4.1 使用进程池

python 复制代码
python
from multiprocessing import Pool
import time
import os

def compute_square(n):
    """CPU密集型任务示例"""
    print(f'进程 {os.getpid()} 计算 {n} 的平方')
    time.sleep(1)  # 模拟耗时计算
    return n * n

if __name__ == '__main__':
    # 创建进程池(默认使用所有可用的CPU核心)
    with Pool(processes=4) as pool:
        # 同步执行(阻塞方式)
        print('--- 同步执行 ---')
        result = pool.apply(compute_square, (5,))
        print(f'同步结果: {result}')
        
        # 异步执行(非阻塞方式)
        print('\n--- 异步执行 ---')
        async_results = []
        
        for i in range(10):
            # 提交任务到进程池
            result = pool.apply_async(compute_square, (i,))
            async_results.append(result)
        
        # 获取异步执行结果
        for i, result in enumerate(async_results):
            print(f'任务{i}结果: {result.get(timeout=5)}')  # 设置超时时间
        
    print('所有任务完成,进程池已自动关闭')

4.2 进程池的高级用法

python 复制代码
python
from multiprocessing import Pool
import time

def process_data(data_chunk):
    """处理数据块的函数"""
    time.sleep(0.5)  # 模拟处理时间
    processed = [x * 2 for x in data_chunk]
    return processed, sum(processed)

def init_worker():
    """工作进程初始化函数"""
    print(f'工作进程初始化完成')

def process_callback(result):
    """回调函数,在主进程中执行"""
    processed_data, total = result
    print(f'处理完成,总和: {total}')

def error_callback(error):
    """错误回调函数"""
    print(f'处理出错: {error}')

if __name__ == '__main__':
    # 准备数据
    data = [list(range(i, i+5)) for i in range(0, 50, 5)]
    
    # 创建进程池,设置初始化函数和最大任务数
    with Pool(
        processes=3,
        initializer=init_worker,
        maxtasksperchild=3  # 每个子进程最多执行3个任务后重启
    ) as pool:
        
        # 使用map方法(顺序不变)
        print('--- 使用map方法 ---')
        results = pool.map(process_data, data[:3])
        for i, result in enumerate(results):
            print(f'数据块{i}: {result}')
        
        # 使用imap方法(惰性求值)
        print('\n--- 使用imap方法 ---')
        for i, result in enumerate(pool.imap(process_data, data[3:6])):
            print(f'数据块{i+3}: {result}')
        
        # 使用apply_async带回调
        print('\n--- 带回调的异步执行 ---')
        async_results = []
        for chunk in data[6:9]:
            result = pool.apply_async(
                process_data,
                args=(chunk,),
                callback=process_callback,
                error_callback=error_callback
            )
            async_results.append(result)
        
        # 等待所有异步任务完成
        for result in async_results:
            result.wait()
    
    print('所有数据处理完成')

五、 进程间通信(IPC)

5.1 管道(Pipe)

python 复制代码
python
from multiprocessing import Process, Pipe
import time

def sender(conn):
    """发送数据的进程"""
    messages = ['Hello', 'World', 'Python', 'Multiprocessing']
    
    for msg in messages:
        print(f'发送者: 发送 {msg}')
        conn.send(msg)
        time.sleep(0.5)
    
    # 发送结束信号
    conn.send(None)
    conn.close()

def receiver(conn):
    """接收数据的进程"""
    while True:
        msg = conn.recv()
        if msg is None:
            break
        print(f'接收者: 收到 {msg}')
        time.sleep(0.3)
    
    conn.close()

if __name__ == '__main__':
    # 创建管道(双向)
    parent_conn, child_conn = Pipe()
    
    # 创建并启动进程
    p1 = Process(target=sender, args=(child_conn,))
    p2 = Process(target=receiver, args=(parent_conn,))
    
    p1.start()
    p2.start()
    
    p1.join()
    p2.join()
    
    print('通信完成')

5.2 队列(Queue)

python 复制代码
python
from multiprocessing import Process, Queue
import time
import random

def producer(queue, name):
    """生产者进程"""
    for i in range(5):
        item = f'{name}_产品{i}'
        print(f'生产者 {name}: 生产 {item}')
        queue.put(item)
        time.sleep(random.uniform(0.1, 0.5))
    
    # 发送结束信号
    queue.put(None)

def consumer(queue, name):
    """消费者进程"""
    while True:
        item = queue.get()
        if item is None:
            # 再次放入None,让其他消费者也能结束
            queue.put(None)
            break
        
        print(f'消费者 {name}: 消费 {item}')
        time.sleep(random.uniform(0.2, 0.8))
    
    print(f'消费者 {name}: 工作完成')

if __name__ == '__main__':
    # 创建队列
    queue = Queue(maxsize=10)  # 最大容量10
    
    # 创建多个生产者和消费者
    producers = [
        Process(target=producer, args=(queue, f'P{i}'))
        for i in range(2)
    ]
    
    consumers = [
        Process(target=consumer, args=(queue, f'C{i}'))
        for i in range(3)
    ]
    
    # 启动所有进程
    for p in producers:
        p.start()
    
    for c in consumers:
        c.start()
    
    # 等待生产者完成
    for p in producers:
        p.join()
    
    # 添加结束信号
    queue.put(None)
    
    # 等待消费者完成
    for c in consumers:
        c.join()
    
    print('所有生产消费任务完成')

六、 进程间同步

python 复制代码
python
from multiprocessing import Process, Lock, Semaphore, Event, Barrier
import time
import os

# 示例1:使用锁(Lock)保护共享资源
def worker_with_lock(lock, worker_id):
    """使用锁同步的工人"""
    with lock:
        print(f'工人 {worker_id} (PID: {os.getpid()}) 开始工作')
        time.sleep(1)
        print(f'工人 {worker_id} (PID: {os.getpid()}) 结束工作')

# 示例2:使用信号量(Semaphore)控制并发数
def worker_with_semaphore(sem, worker_id):
    """使用信号量控制并发"""
    with sem:
        print(f'工人 {worker_id} 获取到许可证,开始工作')
        time.sleep(2)
        print(f'工人 {worker_id} 工作完成,释放许可证')

# 示例3:使用事件(Event)协调多个进程
def waiter(event, waiter_id):
    """等待事件的进程"""
    print(f'等待者 {waiter_id} 正在等待事件...')
    event.wait()
    print(f'等待者 {waiter_id} 检测到事件,开始工作')

def setter(event, setter_id, delay):
    """设置事件的进程"""
    time.sleep(delay)
    print(f'设置者 {setter_id} 设置事件')
    event.set()

# 示例4:使用屏障(Barrier)同步多个进程
def worker_at_barrier(barrier, worker_id):
    """在屏障处同步的工人"""
    print(f'工人 {worker_id} 到达集合点')
    time.sleep(worker_id)  # 模拟不同的到达时间
    barrier.wait()
    print(f'工人 {worker_id} 通过屏障,继续前进')

if __name__ == '__main__':
    print('=== 示例1:锁同步 ===')
    lock = Lock()
    processes = []
    for i in range(3):
        p = Process(target=worker_with_lock, args=(lock, i))
        processes.append(p)
        p.start()
    
    for p in processes:
        p.join()
    
    print('\n=== 示例2:信号量控制 ===')
    sem = Semaphore(2)  # 允许同时2个进程工作
    processes = []
    for i in range(5):
        p = Process(target=worker_with_semaphore, args=(sem, i))
        processes.append(p)
        p.start()
    
    for p in processes:
        p.join()
    
    print('\n=== 示例3:事件协调 ===')
    event = Event()
    processes = []
    
    # 创建3个等待者
    for i in range(3):
        p = Process(target=waiter, args=(event, i))
        processes.append(p)
        p.start()
    
    # 创建设置者
    setter_proc = Process(target=setter, args=(event, 0, 2))
    setter_proc.start()
    processes.append(setter_proc)
    
    for p in processes:
        p.join()
    
    print('\n=== 示例4:屏障同步 ===')
    barrier = Barrier(3)  # 需要3个进程到达才能继续
    processes = []
    for i in range(3):
        p = Process(target=worker_at_barrier, args=(barrier, i))
        processes.append(p)
        p.start()
    
    for p in processes:
        p.join()

七、进程间共享状态

7.1 共享内存(Value和Array)

python 复制代码
python
from multiprocessing import Process, Value, Array
import time

def increment_counter(counter, times):
    """多个进程同时增加计数器"""
    for _ in range(times):
        with counter.get_lock():  # 获取锁保护
            counter.value += 1

def modify_array(arr, process_id):
    """修改共享数组"""
    for i in range(len(arr)):
        arr[i] = process_id * 10 + i
        time.sleep(0.01)

if __name__ == '__main__':
    # 共享值(带锁保护)
    shared_counter = Value('i', 0)  # 'i' 表示整数类型
    
    # 共享数组(带锁保护)
    shared_array = Array('i', 5)  # 长度为5的整数数组
    
    print(f'初始计数器值: {shared_counter.value}')
    print(f'初始数组值: {list(shared_array)}')
    
    # 创建多个进程
    processes = []
    
    # 进程1和2:增加计数器
    for i in range(2):
        p = Process(target=increment_counter, args=(shared_counter, 1000))
        processes.append(p)
        p.start()
    
    # 进程3和4:修改数组
    for i in range(2):
        p = Process(target=modify_array, args=(shared_array, i+1))
        processes.append(p)
        p.start()
    
    # 等待所有进程完成
    for p in processes:
        p.join()
    
    print(f'最终计数器值: {shared_counter.value}')
    print(f'最终数组值: {list(shared_array)}')

7.2 服务进程(Manager)

python 复制代码
python
from multiprocessing import Process, Manager
import time

def worker_modify_dict(shared_dict, worker_id):
    """修改共享字典"""
    shared_dict[f'worker_{worker_id}'] = {
        'pid': Process().pid,
        'start_time': time.time(),
        'data': list(range(worker_id * 3, worker_id * 3 + 3))
    }
    time.sleep(0.5)

def worker_modify_list(shared_list, worker_id):
    """修改共享列表"""
    shared_list.append(f'任务_{worker_id}')
    if len(shared_list) > 5:
        # 注意:需要在Manager的锁保护下操作
        shared_list[:] = shared_list[-5:]  # 只保留最后5个
    time.sleep(0.3)

if __name__ == '__main__':
    with Manager() as manager:
        # 创建共享数据结构
        shared_dict = manager.dict()
        shared_list = manager.list()
        
        print('初始状态:')
        print(f'字典: {dict(shared_dict)}')
        print(f'列表: {list(shared_list)}')
        
        # 创建多个进程操作共享数据
        processes = []
        
        # 创建字典修改进程
        for i in range(3):
            p = Process(target=worker_modify_dict, args=(shared_dict, i))
            processes.append(p)
            p.start()
        
        # 创建列表修改进程
        for i in range(5):
            p = Process(target=worker_modify_list, args=(shared_list, i))
            processes.append(p)
            p.start()
            time.sleep(0.1)  # 交错启动
        
        # 等待所有进程
        for p in processes:
            p.join()
        
        print('\n最终状态:')
        print(f'字典: {dict(shared_dict)}')
        print(f'列表: {list(shared_list)}')

八、 实际应用示例

8.1 并行数据处理

python 复制代码
python
from multiprocessing import Pool, cpu_count
import time
import math

def is_prime(n):
    """判断是否为质数(CPU密集型任务)"""
    if n < 2:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    
    limit = int(math.sqrt(n)) + 1
    for i in range(3, limit, 2):
        if n % i == 0:
            return False
    return True

def process_range(range_start, range_end):
    """处理一个数字范围,统计其中的质数"""
    count = 0
    primes = []
    for num in range(range_start, range_end):
        if is_prime(num):
            count += 1
            primes.append(num)
    return range_start, range_end, count, primes[:10]  # 返回前10个质数

if __name__ == '__main__':
    # 大数据范围
    start_num = 1_000_000
    end_num = 2_000_000
    
    # 分割任务
    num_workers = cpu_count()  # 使用所有CPU核心
    chunk_size = (end_num - start_num) // num_workers
    
    ranges = []
    for i in range(num_workers):
        chunk_start = start_num + i * chunk_size
        chunk_end = start_num + (i + 1) * chunk_size if i < num_workers - 1 else end_num
        ranges.append((chunk_start, chunk_end))
    
    print(f'使用 {num_workers} 个进程并行处理')
    print(f'数字范围: {start_num} - {end_num}')
    print(f'每个进程处理: {chunk_size} 个数字')
    
    # 使用进程池并行处理
    start_time = time.time()
    
    with Pool(processes=num_workers) as pool:
        # 使用starmap处理多个参数
        results = pool.starmap(process_range, ranges)
    
    # 合并结果
    total_primes = 0
    all_primes_sample = []
    
    for chunk_start, chunk_end, count, primes in results:
        total_primes += count
        all_primes_sample.extend(primes)
        print(f'范围 {chunk_start}-{chunk_end}: 找到 {count} 个质数')
    
    elapsed_time = time.time() - start_time
    
    print(f'\n总计找到 {total_primes} 个质数')
    print(f'前20个质数示例: {sorted(all_primes_sample)[:20]}')
    print(f'总耗时: {elapsed_time:.2f} 秒')

九、 注意事项和最佳实践

9.1 重要注意事项

python 复制代码
python
import multiprocessing
import os

def demonstrate_issues():
    print("=== 多进程编程注意事项 ===")
    
    # 1. Windows 和 macOS 上的启动方式
    if __name__ == '__main__':
        print("1. 必须在 __name__ == '__main__' 保护块内启动进程")
        print("   防止子进程无限递归创建")
    
    # 2. 进程间通信开销
    print("\n2. 进程间通信有开销,应尽量减少数据传递")
    print("   大数据传输考虑使用共享内存")
    
    # 3. 资源管理
    print("\n3. 注意进程的资源消耗")
    print(f"   CPU核心数: {os.cpu_count()}")
    print("   创建过多进程可能导致系统资源耗尽")
    
    # 4. 异常处理
    print("\n4. 子进程异常不会自动传递到主进程")
    print("   需要在子进程内部处理异常,或通过IPC传递异常信息")
    
    # 5. 平台差异
    print("\n5. 不同平台的实现有差异:")
    print("   Windows: 使用 spawn 方式启动进程")
    print("   Unix/Linux: 使用 fork 方式启动进程")

if __name__ == '__main__':
    demonstrate_issues()

9.2 最佳实践总结

合理设置进程数量:通常为 CPU 核心数,对于 I/O 密集型任务可适当增加

使用进程池管理:避免频繁创建销毁进程的开销

尽量减少进程间通信:通信开销可能抵消并行带来的收益

使用适当的同步机制:根据需求选择锁、信号量、事件等

正确处理异常和资源清理:确保进程正确退出,释放资源

考虑使用 concurrent.futures:更高级的接口,适合简单场景


总结

通过合理使用 multiprocessing 模块,可以充分利用多核 CPU 的优势,显著提高 Python 程序在处理 CPU 密集型任务时的性能。

相关推荐
小二·28 分钟前
Python Web 开发进阶实战 :AI 原生数字孪生 —— 在 Flask + Three.js 中构建物理世界实时仿真与优化平台
前端·人工智能·python
eWidget1 小时前
InfluxDB迁移至金仓数据库的同城容灾实践:性能显著提升、运维效率优化,某能源企业实现RPO_5秒的高可靠时序数据管理
运维·数据库·能源·时序数据库·kingbase·kingbasees·金仓数据库
小句1 小时前
MySQL慢查询日志详细使用指南
数据库·mysql·adb
hmywillstronger1 小时前
【Rhino】【Python】 查询指定字段并cloud标注
开发语言·python
老邓计算机毕设2 小时前
SSM医疗资源普查6qxol(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架·javaweb开发·医疗资源管理
dyyx1112 小时前
如何从Python初学者进阶为专家?
jvm·数据库·python
二十雨辰2 小时前
[python]-函数
开发语言·python
CryptoRzz2 小时前
如何高效接入日本股市实时数据?StockTV API 对接实战指南
java·python·kafka·区块链·状态模式·百度小程序
码农水水2 小时前
中国邮政Java面试被问:容器镜像的多阶段构建和优化
java·linux·开发语言·数据库·mysql·面试·php
曹牧2 小时前
Oracle:NULL
数据库·oracle