如何通过并发性和并行性增强 Python 代码

核心要点

  • 并发与并行是计算机科学中处理任务执行的两种基本模式,它们各自具备独特的特性。
  • 并发性侧重于高效利用资源并提升应用程序的响应速度,而并行性则致力于实现最佳性能和扩展能力。
  • Python提供了多种处理并发的方案,例如使用 asyncio 库进行线程操作和异步编程,以及利用 multiprocessing 模块实现并行处理。

并发和并行是两种能够让程序同时执行的技术。Python 提供了多种处理并发和并行任务的选项,这可能会让初学者感到困惑。

本文将探讨在 Python 中实现并发和并行处理的工具和库,以及它们之间的区别。

理解并发与并行

并发和并行代表了计算任务执行的两种基本策略,每一种都有其独特的运行方式。

  • 并发指的是程序能够同时管理多个任务,但并非真正同时执行它们。它通过交替执行任务的方式,让任务看起来像是同时进行。
  • 并行则涉及真正同时执行多个任务,通常会利用多个 CPU 核心或处理器。并行实现了真正的同步执行,能够更快地完成任务,特别适用于计算密集型操作。
  • 并发与并行性的重要性

    在计算领域,并发性和并行性的重要性再怎么强调也不为过。以下是这些技术至关重要的原因:

  • 资源利用:并发可以高效地利用系统资源,确保任务能够积极进展,而不是在等待外部资源时闲置。
  • 响应能力:并发可以提高应用程序的响应速度,特别是在涉及用户界面或 Web 服务器的场景中。
  • 性能:并行性对于实现最佳性能至关重要,尤其是在处理复杂计算、数据处理和模拟等 CPU 密集型任务时。
  • 可扩展性:并发性和并行性对于构建可扩展的系统至关重要。
  • 面向未来:随着硬件趋势继续倾向于多核处理器,有效利用并行处理能力将变得越来越重要。
  • Python 中的并发

    在 Python 中,您可以使用 asyncio 库的线程和异步编程来实现并发。

    Python 中的线程

    线程是一种 Python 并发机制,允许您在单个进程中创建和管理多个任务。 线程特别适用于那些受 I/O 限制的任务,这些任务可以从并发执行中受益。

    Python 的 threading 模块提供了创建和管理线程的高级接口。虽然由于全局解释器锁(GIL)的限制,线程无法实现真正的并行,但它们仍然可以通过有效交错任务来实现并发。

    以下代码展示了如何使用线程实现并发的示例。它使用 Python 的 requests 库发送 HTTP 请求,这是一种常见的 I/O 阻塞任务。 它还使用了 time 模块来测量执行时间。

     import requests
    import time
    import threading

    urls = [
        'https://www.google.com',
        'https://www.wikipedia.org',
        'https://www.makeuseof.com',
    ]


    def download_url(url):
        response = requests.get(url)
        print(f"已下载 {url} - 状态码: {response.status_code}")


    start_time = time.time()

    for url in urls:
        download_url(url)

    end_time = time.time()
    print(f"顺序下载耗时 {end_time - start_time:.2f} 秒\n")


    start_time = time.time()
    threads = []

    for url in urls:
        thread = threading.Thread(target=download_url, args=(url,))
        thread.start()
        threads.append(thread)


    for thread in threads:
        thread.join()

    end_time = time.time()
    print(f"线程下载耗时 {end_time - start_time:.2f} 秒")

    运行这段程序,您应该能够看到使用线程发起的请求比顺序请求快得多。即使差异只有几分之一秒,但在使用线程执行 I/O 密集型任务时,性能提升也是非常显著的。

    使用 asyncio 进行异步编程

    asyncio 提供了一个事件循环,用于管理被称为协程的异步任务。协程是一种可以暂停和恢复的函数,因此它们非常适合 I/O 密集型任务。该库对于那些需要等待外部资源(例如网络请求)的场景特别有用。

    您可以修改前面的请求发送示例以使用 asyncio:

     import asyncio
    import aiohttp
    import time

    urls = [
        'https://www.google.com',
        'https://www.wikipedia.org',
        'https://www.makeuseof.com',
    ]


    async def download_url(url):
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as response:
                content = await response.text()
                print(f"已下载 {url} - 状态码: {response.status}")


    async def main():
        
        tasks = [download_url(url) for url in urls]

        
        await asyncio.gather(*tasks)

    start_time = time.time()


    asyncio.run(main())

    end_time = time.time()

    print(f"Asyncio 下载耗时 {end_time - start_time:.2f} 秒")

    使用这段代码,您可以利用 asyncio 同时下载网页,并利用异步 I/O 操作。对于 I/O 密集型任务来说,这比线程更加高效。

    Python 中的并行性

    您可以使用 Python 的 multiprocessing 模块来实现并行处理,从而充分利用多核处理器。

    Python 中的多处理

    Python 的 multiprocessing 模块提供了一种通过创建独立的进程来实现并行性的方法,每个进程都有自己的 Python 解释器和内存空间。 这有效地绕过了全局解释器锁(GIL)的限制,因此非常适合 CPU 密集型任务。

     import requests
    import multiprocessing
    import time

    urls = [
        'https://www.google.com',
        'https://www.wikipedia.org',
        'https://www.makeuseof.com',
    ]


    def download_url(url):
        response = requests.get(url)
        print(f"已下载 {url} - 状态码: {response.status_code}")

    def main():
        
        num_processes = len(urls)
        pool = multiprocessing.Pool(processes=num_processes)

        start_time = time.time()
        pool.map(download_url, urls)
        end_time = time.time()

        
        pool.close()
        pool.join()

        print(f"多进程下载耗时 {end_time-start_time:.2f} 秒")

    main()

    在此示例中,multiprocessing 模块会创建多个进程,允许 download_url 函数并行运行。

    何时使用并发或并行

    并发和并行之间的选择取决于任务的性质以及可用的硬件资源。

    当处理 I/O 密集型任务(例如读取和写入文件或发出网络请求)并且需要考虑内存限制时,并发可能是一个更好的选择。

    当您的 CPU 密集型任务可以从真正的并行性中受益,并且任务之间具有高度隔离性(其中一个任务的失败不应影响其他任务)时,请使用多处理。

    充分利用并发和并行性

    并行性和并发性是提高 Python 代码响应速度和性能的有效方法。 理解这些概念之间的区别并选择最有效的策略至关重要。

    无论您处理的是 CPU 密集型进程还是 I/O 密集型进程,Python 都提供了必要的工具和模块,使您的代码通过并发或并行变得更加高效。