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 密集型任务时的性能。

相关推荐
我不想名字重复几秒前
redis缓存和数据库数据保持一致
数据库·redis·缓存
宇宙第一小趴菜21 分钟前
三、Oracle 核心原理
数据库·oracle
小大宇30 分钟前
Python 集成 Conda
开发语言·python·conda
初学者,亦行者1 小时前
利用Pyecharts绘制堆叠柱状图
python·信息可视化·数据分析
林泽毅1 小时前
PyTRIO:当强化学习不再需要本地GPU
人工智能·python·深度学习·机器学习
小陈phd1 小时前
QAnything 阅读优化策略05——检索
人工智能·python·机器学习
ruofu331 小时前
wsl端是py312,但是项目是py311, 如何下载py311的依赖包(wheels)
python·docker
码出钞能力1 小时前
apache-shardingsphere-5.5.3自定义分片算法
数据库
小大宇2 小时前
python flask框架 SSE流式返回、跨域、报错
开发语言·python·flask