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

要点

  • 并发和并行是计算中任务执行的基本原则,两者都有其独特的特征。
  • 并发性可以实现高效的资源利用并提高应用程序的响应能力,而并行性对于最佳性能和可扩展性至关重要。
  • Python 提供了处理并发的选项,例如使用 asyncio 的线程和异步编程,以及使用多处理模块的并行性。

并发和并行是两种可让您同时运行多个程序的技术。 Python 有多种并发和并行处理任务的选项,这可能会令人困惑。

探索可用于在 Python 中正确实现并发和并行性的工具和库,以及它们的不同之处。

了解并发和并行性

并发和并行是指计算中任务执行的两个基本原则。 每个都有其独特的特点。

  • 并发性是程序同时管理多个任务而不必同时执行它们的能力。 它围绕交错任务的想法,以一种看似同时的方式在任务之间切换。
  • 另一方面,并​​行性涉及真正并行执行多个任务。 它通常利用多个 CPU 核心或处理器。 并行实现了真正的同时执行,让您可以更快地执行任务,并且非常适合计算密集型操作。
  • 并发和并行的重要性

    计算中对并发性和并行性的需求怎么强调都不为过。 这就是为什么这些技术很重要:

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

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

    Python 中的线程

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

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

    下面的代码显示了使用线程实现并发的示例。 它使用Python请求库发送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"Downloaded {url} - Status Code: {response.status_code}")


    start_time = time.time()

    for url in urls:
        download_url(url)

    end_time = time.time()
    print(f"Sequential download took {end_time - start_time:.2f} seconds\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"Threaded download took {end_time - start_time:.2f} seconds")

    运行该程序,您应该会看到线程请求比顺序请求快了多少。 尽管差异只有几分之一秒,但在使用线程执行 I/O 密集型任务时,您可以清楚地感受到性能的提高。

    使用 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"Downloaded {url} - Status Code: {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 download took {end_time - start_time:.2f} seconds")

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

    Python 中的并行性

    您可以使用以下方式实现并行性 Python 的多处理模块,这使您可以充分利用多核处理器。

    Python 中的多处理

    Python 的多处理模块提供了一种通过创建单独的进程来实现并行性的方法,每个进程都有自己的 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"Downloaded {url} - Status Code: {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"Multiprocessing download took {end_time-start_time:.2f} seconds")

    main()

    在此示例中,多处理生成多个进程,允许 download_url 函数并行运行。

    何时使用并发或并行

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

    在处理 I/O 密集型任务(例如读取和写入文件或发出网络请求)以及需要考虑内存限制时,可以使用并发。

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

    利用并发和并行性

    并行性和并发性是提高 Python 代码响应能力和性能的有效方法。 了解这些概念之间的差异并选择最有效的策略非常重要。

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