手把手教你玩转 multiprocessing,让程序跑得飞起

很多人学 Python 时,都会遇到一个迷惑: 明明我的电脑是 8 核 16 线程的猛兽,为什么 Python 跑起来还是跟蜗牛一样?

答案就是三个字母 ------ GIL(全局解释器锁)

这个"锁"就像一个狭窄的单行道,哪怕有再多的车(线程),一次也只能过一辆。于是你会发现,写了再多的多线程代码,CPU 占用率还是死活上不去。

那么问题来了:想让 Python 代码跑得飞快,能真正用上多核 CPU,我们该怎么办? ------答案就是:用多进程(multiprocessing)

这篇文章,我会带你从零开始搞懂 Python 的多进程:从创建子进程,到进程间通信,再到进程池优化。代码都有,讲解也够接地气。看完你会发现,多进程其实并不难,而且真的是性能杀手锏。


一、为什么非得用多进程?

先举个真实的例子:

有个朋友写了个爬虫,用多线程抓数据,本来以为能飞快采集。结果 CPU 占用率才 20%,剩下的 80% 跟喝咖啡去了。原因就是 Python 的线程被 GIL 卡住了,线程不是同时跑,而是轮流跑

而多进程完全不同。每个进程就像一个独立的工厂,操作系统调度它们"并行"运行。你开 4 个进程,理论上就是 4 倍速度。尤其在做 CPU 密集型任务(计算、图像处理、数据分析) 时,多进程的效果肉眼可见。

所以一句话:

  • I/O 密集型 → 多线程(比如爬虫、网络请求)
  • CPU 密集型 → 多进程(比如矩阵计算、加密解密)

搞清楚这个原则,你就不会乱用。


二、最简单的多进程示例

说一千道一万,不如上代码。

下面这个例子,你可以直接跑:

scss 复制代码
import multiprocessing as mp
import time

def worker(name):
    print(f"{name} 进程启动")
    time.sleep(2)
    print(f"{name} 进程结束")

if __name__ =='__main__':
    p1 = mp.Process(target=worker, args=("进程1",))
    p2 = mp.Process(target=worker, args=("进程2",))
    
    p1.start()
    p2.start()
    
    p1.join()
    p2.join()
    print("主进程结束")

运行结果你会发现:两个子进程几乎同时启动,而不是一个等另一个结束才开始。

这就是"并行"的魅力。


三、除了函数,还能继承类来写

有些场景下,我们可能想要把进程封装得更清晰,可以直接继承 Process 来写:

python 复制代码
import multiprocessing as mp
import time

class MyProcess(mp.Process):
    def __init__(self, name):
        super().__init__()
        self.name = name
        
    def run(self):
        print(f"{self.name} 执行中")
        time.sleep(1)
        print(f"{self.name} 退出")

if __name__ =='__main__':
    p = MyProcess("自定义进程")
    p.start()
    p.join()

这种写法更 OOP,一看就是"我要定义一个特殊的进程",代码也更容易扩展。


四、进程之间怎么交流?

你可能会问:进程之间内存不共享,那它们怎么交换数据?

答案就是三种方式:队列(Queue)、管道(Pipe)、共享内存(Value/Array)

1. 队列(推荐)

就像工厂里的传送带,一个进程负责放东西,另一个负责拿:

python 复制代码
def producer(q):
    for i in range(5):
        q.put(f"消息 {i}")
        print(f"生产: 消息 {i}")

def consumer(q):
    while True:
        item = q.get()
        if item is None: break # 终止信号
        print(f"消费: {item}")

if __name__ =='__main__':
    queue = mp.Queue()
    
    p1 = mp.Process(target=producer, args=(queue,))
    p2 = mp.Process(target=consumer, args=(queue,))
    
    p1.start()
    p2.start()
    
    p1.join()
    queue.put(None) # 发送终止信号
    p2.join()

这种方式最实用,特别适合生产者-消费者模型


2. 管道(Pipe)

如果只是"一对一"通信,管道就够了:

scss 复制代码
def child_process(conn):
    conn.send("子进程消息")
    print("子进程收到:", conn.recv())
    conn.close()

if __name__ =='__main__':
    parent_conn, child_conn = mp.Pipe()

    p = mp.Process(target=child_process, args=(child_conn,))
    p.start()

    print("父进程收到:", parent_conn.recv())
    parent_conn.send("父进程消息")

    p.join()

3. 共享内存(Value / Array)

这种方式更高效,但只适合存放简单数据:

ini 复制代码
def modify(n, arr):
    n.value = 3.14
    for i in range(len(arr)):
        arr[i] = arr[i] * 2

if __name__ =='__main__':
    num = mp.Value('d', 0.0)    # 'd' 表示双精度浮点
    arr = mp.Array('i', range(5))    # 'i' 表示有符号整型

    p = mp.Process(target=modify, args=(num, arr))
    p.start()
    p.join()

    print(num.value)    # 输出: 3.14
    print(arr[:])       # 输出: [0, 2, 4, 6, 8]

五、进程同步:别打架!

多进程最大的难点之一,就是同步。 比如多个进程同时修改一个共享变量,很可能出问题。

解决办法:锁、信号量、条件变量

这里重点给你演示锁的用法:

scss 复制代码
def increment(lock, counter):
    for _ in range(1000):
        with lock:
            counter.value += 1

if __name__ =='__main__':
    lock = mp.Lock()
    counter = mp.Value('i', 0)
    
    processes = [mp.Process(target=increment, args=(lock, counter)) for _ in range(4)]
    
    for p in processes: p.start()
    for p in processes: p.join()
    
    print("最终值:", counter.value) # 正确输出 4000

如果不用锁,你会发现结果完全不对。这就是"线程安全"同样存在于进程中的原因。


六、进程池:批量任务的神器

如果你需要一次性处理很多任务,比如计算一堆平方数,用进程池就很方便:

python 复制代码
def square(x):
    return x * x

if __name__ =='__main__':
    with mp.Pool(processes=4) as pool: # 创建4个工作进程
        results = pool.map(square, range(10))
        print("map结果:", results) # [0, 1, 4, 9, ..., 81]
        
        async_result = pool.apply_async(square, (5,))
        print("异步结果:", async_result.get()) # 25
        
        for res in pool.imap(square, range(5)):
            print("迭代结果:", res)

相比自己管理进程,Pool 更轻松,尤其是需要反复执行任务的情况。


七、实战:生产者-消费者模型

最后给你一个完整例子,经典的"生产-消费"模型:

python 复制代码
import multiprocessing as mp
import time

def producer(queue, items):
    for item in items:
        print(f"生产: {item}")
        queue.put(item)
        time.sleep(0.1)
    queue.put(None) # 结束信号

def consumer(queue):
    while True:
        item = queue.get()
        if item is None: break
        print(f"消费: {item}")
        time.sleep(0.2)

if __name__ =='__main__':
    queue = mp.Queue(maxsize=3) # 限制队列大小
    
    items =[f"产品-{i}" for i in range(10)]
    
    prod = mp.Process(target=producer, args=(queue, items))
    cons = mp.Process(target=consumer, args=(queue,))
    
    prod.start()
    cons.start()
    
    prod.join()
    cons.join()
    print("生产消费完成")

这个例子一看就能用到实战里,比如日志处理、任务调度等。


八、踩坑经验分享

  • Windows 必须写 if __name__ == '__main__' ,否则会无限创建进程。
  • 不要滥用共享内存,能用消息队列就别用锁。
  • 记得 .join() ,否则容易变成僵尸进程。
  • 多进程调试麻烦,最好模块化开发,逐步加复杂度。
  • 大任务拆小块,通信尽量少,否则传输开销可能大过运算。

九、总结

如果你能把本文的例子都敲一遍,就已经基本掌握 Python 多进程编程了。 简单回顾一下:

  • 为什么要多进程:突破 GIL,利用多核 CPU。
  • 基础用法Process、类继承。
  • 进程通信:Queue / Pipe / Value。
  • 进程同步:Lock / Semaphore / Condition。
  • 进程池:批量任务利器。
  • 最佳实践:生产者-消费者模型。

一句话总结: 👉 CPU 密集型任务就用多进程,I/O 密集型任务就用多线程

掌握这套方法,你写 Python 程序的速度,真的能"起飞"。

相关推荐
li理4 分钟前
鸿蒙 Next 布局开发实战:6 大核心布局组件全解析
前端
EndingCoder5 分钟前
React 19 与 Next.js:利用最新 React 功能
前端·javascript·后端·react.js·前端框架·全栈·next.js
li理7 分钟前
鸿蒙 Next 布局大师课:从像素级控制到多端适配的实战指南
前端
RainbowJie110 分钟前
Gemini CLI 与 MCP 服务器:释放本地工具的强大潜力
java·服务器·spring boot·后端·python·单元测试·maven
前端赵哈哈11 分钟前
Vite 图片压缩的 4 种有效方法
前端·vue.js·vite
工作碎碎念12 分钟前
NumPy------数值计算
python
Nicholas6818 分钟前
flutter滚动视图之ScrollView源码解析(五)
前端
工作碎碎念20 分钟前
pandas
python
电商API大数据接口开发Cris20 分钟前
Go 语言并发采集淘宝商品数据:利用 API 实现高性能抓取
前端·数据挖掘·api
风中凌乱的L25 分钟前
vue 一键打包上传
前端·javascript·vue.js