Python中的并行处理


介绍

在当今快节奏的数字环境中,有效完成计算密集型任务对于开发人员和数据科学家至关重要。幸运的是,Python 凭借其适应性和广泛的生态系统,提供了强大的并行处理能力。通过将复杂问题分解成更小、更易于管理的任务,这些任务可以并发执行,我们可以获得巨大的性能提升。

Python 的并行处理功能使我们能够通过利用可用的计算资源,更快速、更有效地处理诸如网络抓取、科学模拟和数据分析等任务。在这篇文章中,我们将踏上 Python 并行处理之旅。我们将探索多种方法,包括多进程、异步编程和多线程,并学习如何有效地利用它们来克服系统中的性能瓶颈。加入我们,一起释放 Python 并行处理的全部潜能,并达到性能和生产力的新高度。

了解并行处理

并行处理是指将一项任务分解成较小的子任务,并在多个处理器或内核上并发执行这些子任务。通过有效利用可用的计算资源,并行处理可以显著减少程序的总执行时间。Python 提供了多种并行处理方法,包括异步编程、多进程和多线程。

Python 中的多线程

多线程方法利用单个进程内的多个线程并发运行,同时共享相同的内存。Python 的 threading 模块可以轻松实现多线程。然而,由于全局解释器锁 (GIL),Python 中的多线程对于 CPU 密集型操作可能不会带来明显的加速,因为 GIL 每次只允许一个线程执行 Python 字节码。但是,多线程对于 I/O 密集型任务很有用,因为它允许线程在等待 I/O 操作完成时执行其他操作。

让我们来看一个使用多线程下载多个网页的示例

示例

import threading import requests 
 
def download_page(url): 
    response = requests.get(url)    
print(f"Downloaded {url}") 
 
urls = [ 
    "https://example.com", 
    "https://google.com", 
    "https://openai.com" 
] 
 
threads = [] 
 for url in 
 urls: 
    thread = threading.Thread(target=download_page,
args=(url,))     thread.start()    threads.append(thread) 
 
for thread in threads: 
    thread.join() 

输出

Downloaded https://example.com 
Downloaded https://google.com 
Downloaded https://openai.com 

上面的代码片段通过在自己的线程中下载每个 URL,实现了多个下载的并发执行。join() 函数确保主线程在每个线程完成之前等待。

Python 中的多进程

与多线程不同,多进程通过使用多个进程(每个进程都有自己的内存空间)来提供真正的并行性。Python 的 multiprocessing 模块提供了一个用于实现多进程的高级接口。多进程适用于 CPU 密集型任务,因为每个进程都在一个单独的 Python 解释器中运行,从而避免了 GIL 对多线程的限制。

下面的代码使用了多进程。一旦 pool 类创建了一个工作进程池,map() 方法就会将任务分配到可用的进程中。results 列表是结果的集合。

考虑下面的示例,我们使用多进程对列表中的每个整数进行平方运算

示例

import multiprocessing 
 
def square(number):    
return number ** 2 
 
numbers = [1, 2, 3, 4, 5] 
 
with multiprocessing.Pool() as pool: 
    results = pool.map(square, numbers) 
 
print(results) 

输出

[1, 4, 9, 16, 25] 

Python 中的异步编程

通过利用非阻塞操作,异步编程可以有效地执行 I/O 密集型任务。借助 asyncio 包,可以使用协程、事件循环和期货来创建 Python 中的异步代码。随着网络应用程序和 API 的日益普及,异步编程变得越来越重要。

下面的代码示例中,fetch_page() 协程使用 aiohttp 异步获取网页。main() 方法创建了一个任务列表,然后使用 asyncio.gather() 并发执行这些任务。await 关键字用于等待任务完成并获取结果。

让我们来看一个使用 asyncio 和 aiohttp 异步获取多个网页的示例

示例

import asyncio 
import aiohttp 
 
async def fetch_page(url):     async with aiohttp.ClientSession() as session:         async with session.get(url) as response: 
            return await response.text() 
 
async def main(): 
    urls = [ 
        "https://example.com", 
        "https://google.com", 
        "https://openai.com" 
    ] 
 
    tasks = [fetch_page(url) for url in urls]     pages = await asyncio.gather(*tasks)     
print(pages) 
 
asyncio.run(main()) 

输出

['<!doctype html>\n<html>\n<head>\n    <title>Example Domain</title>\n\n    <meta 
charset="utf-8" />\n    <meta http-equiv="Content-type"content="text/html; charset=utf-8" />\n    <meta name="viewport" content="width=device-width, initialscale=1" />\n    <style type="text/css">\n    body {\n        background-color: #f0f0f2;\n  margin: 0;\n        padding: 0;\n        font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;\n        \n    }\n    div {\n        width: 600px;\n        margin: 5em auto;\n  padding: 50px;\n        background-color: #fff;\n        border-radius: 1em;\n    }\n    a:link, a:visited {\n        color: #38488f;\n        text-decoration: none;\n    }\n    @media (maxwidth: 700px) {\n        body {\n            background-color: #fff;\n        }\n        div {\n  width: auto;\n            margin: 0 auto;\n            border-radius: 0;\n            padding: 1em;\n        }\n    }\n    </style>    \n</head>\n\n<body>\n<div>\n    <h1>Example Domain</h1>\n    <p>This domain is for use in illustrative examples in documents. You may use this\n    domain in literature without prior coordination or asking for permission.</p>\n    <p><a href="https://www.iana.org/domains/example">More information...</a></p>\n</div>\n</body>\n</html>', '<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="en"><head><meta content="Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for." name="description"><meta content="noodp" name="robots"><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"><meta content="/logos/doodles/2021/mom-
and-dad-6116550989716480.2-law.gif" itemprop="image"><link href="/logos/doodles/2021/mom-and-dad-6116550989716480.2-law.gif" rel="icon" type="image/gif"><title>Google</title><script nonce="sJwM0Ptp5a/whzxPtTD8Yw==">(function(){window.google={kEI:'cmKgYY37A7 K09QPhzKuACw',kEXPI:'1354557,1354612,1354620,1354954,1355090,1355493,13556
83,3700267,4029815,4031109,4032677,4036527,4038022,4043492,4045841,4048347,4
048490,4052469,4055589,4056520,4057177,4057696,4060329,4060798,4061854,4062 531,4064696,406 '

选择正确的方法

Python 的并行处理技术根据手头任务的具体情况而有所不同。以下是一些指导原则,可帮助您做出明智的决策

对于 I/O 密集型任务,其中大部分执行时间都花费在等待输入/输出操作上,多线程是合适的。它非常适合诸如下载文件、使用 API 和操作文件等任务。由于 Python 的全局解释器锁 (GIL),多线程可能不会显著加速 CPU 密集型任务。

另一方面,多进程非常适合涉及密集计算的 CPU 密集型任务。它通过利用多个进程(每个进程都有自己的内存空间)来实现真正的并行性,并绕过了 GIL 的限制。但是,它在内存消耗和进程间通信方面会产生额外的开销。

对于涉及网络操作的 I/O 密集型任务,使用 asyncio 等库执行的异步编程很有用。它利用非阻塞 I/O 操作,以便任务可以继续进行,而无需等待每个操作完成。此方法有效地管理多个并发连接,使其适用于网络服务器开发、Web API 交互和网络抓取。异步编程最大程度地减少了 I/O 操作的等待时间,确保响应能力和可扩展性。

结论

Python 的并行处理能力提供了机会来提高需要复杂计算的任务的效率。无论您选择使用多线程、多进程还是异步编程,Python 都提供了必要的工具和模块来有效地利用并发性。通过理解您的任务的性质并选择合适的方法,您可以最大限度地利用并行处理的优势并缩短执行时间。因此,请大胆探索,充分利用 Python 的并行性来创建更快、更高效的应用程序。

更新于:2023-07-25

5K+ 阅读量

开启你的 职业生涯

通过完成课程获得认证

开始学习
广告