背景简介

Python以其简洁易读的语法和强大的库支持闻名,然而,在追求极致性能的场景中,Python的一些特性也可能成为瓶颈。本篇博客将探讨如何在Python中处理CPU和IO密集型任务,特别是如何利用多线程、多进程和异步执行来提高性能。

多线程的局限性与GIL的影响

Python中的全局解释器锁(GIL)确保了同一时刻只有一个线程执行Python字节码,这使得在多线程环境下,CPU密集型任务得不到预期的性能提升。如下所示代码段:

tot = stats['et'] - stats['st']
for i in inputs:
    assert i[0] == int(i[1])
return tot

通过实验我们发现,线程对于IO任务有帮助,但对于CPU任务则效果甚微。原因在于GIL的存在:

import time
from tasker import cputask, iotask
from random import randint

def proc_iotask(i, outq):
    i[1] = iotask(i[0])
    outq.put(i)

def proc_cputask(i, outq):
    res = cputask(i[0])
    outq.put((i[0], res))

多进程的力量

为了克服GIL带来的限制,多进程成为了一个有效的解决方案。多进程通过在不同的进程间分配任务,让每个进程拥有自己的Python解释器和内存空间,从而绕过GIL的限制。在多进程执行中,进程间的通信成本比线程间要高,但CPU密集型任务的执行时间大为减少。以下展示了多进程的实现:

import multiprocessing
from tasker import cputask, iotask
from random import randint

def proc_iotask(i, outq):
    i[1] = iotask(i[0])
    outq.put(i)

def proc_cputask(i, outq):
    res = cputask(i[0])
    outq.put((i[0], res))

def process(rep, case=None):
    stats.clear()
    inputs = [[randint(1, 1000), None] for i in range(rep)]
    outq = multiprocessing.Queue()
    processes = []
    if 'cpu' == case:
        # 创建多个进程处理CPU任务
    # ...
    stats['st'] = stats.get('st', time.time())
    for t in processes:
        t.start()
    for t in processes:
        t.join()
    stats['et'] = stats.get('et', time.time())
    tot = stats['et'] - stats['st']
    return tot

异步执行的崛起

异步编程允许程序在等待IO操作完成时继续执行其他任务,这对于IO密集型应用来说是一种革命性的优化方法。Python 3.5引入了async和await关键字,使得编写异步代码更为简单和直观。异步执行的代码示例如下:

import asyncio
from tasker import cputask, async_iotask
from random import randint

async def async_iotask(num, loop=None):
    res = await aiohttp.get(URL % str(num[0]), loop=loop)
    text = await res.text()
    num[1] = int(text)
    return text

async def main(rep, case=None, loop=None, inputs=None):
    stats.clear()
    stats['st'] = time.time()
    # 执行CPU密集型或IO密集型任务
    # ...
    stats['et'] = time.time()
    return tot

总结与启发

在优化Python程序性能时,我们需要根据任务的性质选择合适的并发执行模型。对于IO密集型任务,线程和异步执行都是有效的选择,而对于CPU密集型任务,多进程提供了一个切实可行的解决方案。尽管异步编程在IO操作上表现优异,但其在CPU密集型任务中受限于其设计。因此,合理利用Python的并发特性,能够帮助我们编写出既高效又易于维护的代码。

Logo

更多推荐