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 的并行性来创建更快、更高效的应用程序。