并发编程:使用多线程与多进程提升 Python 程序性能

并发编程:使用多线程与多进程提升 Python 程序性能

随着计算机硬件的不断发展,单核 CPU 已逐渐成为过去,多核 CPU 已经成为主流。为了充分利用计算机的性能,我们需要使用 并发编程 来让程序执行得更快。Python 提供了多线程和多进程的支持,让开发者可以通过并发方式提升程序性能。

本篇文章将详细介绍 Python 并发编程的核心概念,包括 多线程多进程 的区别、优缺点、适用场景,并提供多个实战示例,帮助你更好地理解和使用并发编程来优化程序性能。


一、并发编程的基本概念

1.1 并发与并行

  • 并发 (Concurrency):多个任务在一段时间内交替执行,但同一时刻只有一个任务运行。
  • 并行 (Parallelism):多个任务在同一时刻同时运行,利用多核 CPU 并行处理多个任务。
  • 1.2 多线程与多进程

  • 线程 (Thread):线程是操作系统调度的最小单位。多个线程可以共享同一进程的资源,开销较小,但受限于 Python 的 GIL (全局解释器锁)
  • 进程 (Process):进程是操作系统分配资源的基本单位。多个进程之间资源独立,互不影响,适合 CPU 密集型任务,但创建进程的开销较大。
  • GIL 限制
    Python 的 GIL (Global Interpreter Lock) 是一把全局锁,用于确保同一时刻只有一个线程在执行 Python 字节码。由于 GIL 的存在,多线程在 CPU 密集型任务 中无法充分利用多核 CPU。


    二、Python 多线程编程

    2.1 使用 threading 模块

    Python 提供了 threading 模块来创建和管理线程,适合 IO 密集型任务,例如网络请求、文件读写等。

    示例:多线程下载多个网页
    import threading
    import requests
    import time
    
    # 要下载的网页列表
    URLS = [
        'https://www.example.com',
        'https://www.python.org',
        'https://www.github.com',
    ]
    
    # 下载网页的函数
    def download_page(url):
        print(f"开始下载:{url}")
        response = requests.get(url)
        print(f"{url} 下载完成,长度:{len(response.text)}")
    
    # 多线程实现
    start_time = time.time()
    threads = []
    
    for url in URLS:
        thread = threading.Thread(target=download_page, args=(url,))
        thread.start()
        threads.append(thread)
    
    # 等待所有线程结束
    for thread in threads:
        thread.join()
    
    end_time = time.time()
    print(f"总耗时:{end_time - start_time:.2f} 秒")
    

    说明

  • 使用 threading.Thread 创建线程,通过 start() 启动线程。
  • join() 确保主线程等待所有子线程执行完毕。
  • 适用场景:多线程适合 IO 密集型任务,如网络请求、文件读写等。


    三、Python 多进程编程

    3.1 使用 multiprocessing 模块

    multiprocessing 模块允许在多个 CPU 核心上运行多个进程,可以绕过 Python 的 GIL,适合 CPU 密集型任务,如计算密集型操作。

    示例:多进程计算密集型任务

    假设我们需要计算一组数字的平方和:

    import multiprocessing
    import time
    
    # 计算一组数字的平方和
    def compute_square(numbers):
        result = sum(x * x for x in numbers)
        print(f"结果:{result}")
    
    if __name__ == "__main__":
        numbers = list(range(10000000))
        num_processes = 4  # 进程数
        chunk_size = len(numbers) // num_processes
        chunks = [numbers[i:i + chunk_size] for i in range(0, len(numbers), chunk_size)]
    
        # 启动多进程
        start_time = time.time()
        processes = []
    
        for chunk in chunks:
            process = multiprocessing.Process(target=compute_square, args=(chunk,))
            process.start()
            processes.append(process)
    
        for process in processes:
            process.join()
    
        end_time = time.time()
        print(f"总耗时:{end_time - start_time:.2f} 秒")
    

    说明

  • 使用 multiprocessing.Process 创建子进程,通过 start() 启动进程。
  • join() 确保主进程等待所有子进程执行完毕。
  • 将数据分成多个块,每个块交由一个进程处理。
  • 适用场景:多进程适合 CPU 密集型任务,如大规模计算、图像处理等。


    四、线程池与进程池

    对于大量任务,手动管理线程和进程可能会很麻烦。Python 提供了 线程池进程池,用于批量管理线程和进程。

    4.1 使用 concurrent.futures 的线程池

    线程池会自动管理线程的创建与销毁,简化了多线程的使用。

    from concurrent.futures import ThreadPoolExecutor
    import requests
    import time
    
    URLS = [
        'https://www.example.com',
        'https://www.python.org',
        'https://www.github.com',
    ]
    
    def download_page(url):
        response = requests.get(url)
        print(f"{url} 下载完成,长度:{len(response.text)}")
    
    start_time = time.time()
    
    # 使用线程池
    with ThreadPoolExecutor(max_workers=3) as executor:
        executor.map(download_page, URLS)
    
    end_time = time.time()
    print(f"总耗时:{end_time - start_time:.2f} 秒")
    

    说明

  • ThreadPoolExecutor 管理线程池,通过 map 提交任务。
  • max_workers 设置线程数量。
  • 4.2 使用 concurrent.futures 的进程池

    进程池适用于 CPU 密集型任务。

    from concurrent.futures import ProcessPoolExecutor
    import time
    
    def compute_square(n):
        return sum(x * x for x in range(n))
    
    if __name__ == "__main__":
        numbers = [10**6, 10**6, 10**6, 10**6]
    
        start_time = time.time()
    
        # 使用进程池
        with ProcessPoolExecutor(max_workers=4) as executor:
            results = executor.map(compute_square, numbers)
    
        print(list(results))
    
        end_time = time.time()
        print(f"总耗时:{end_time - start_time:.2f} 秒")
    

    五、常见问题与排查技巧

    5.1 GIL 限制

    多线程在 Python 中受到 GIL 限制,导致无法充分利用多核 CPU。因此,对于 CPU 密集型任务,建议使用多进程代替多线程。

    5.2 线程安全问题

    在多线程编程中,多个线程共享同一块内存,可能导致数据竞争。解决方案:

  • 使用线程锁 threading.Lock
  • 尽量避免共享状态,使用队列 queue.Queue
  • 5.3 进程间通信

    多进程的内存是独立的,无法直接共享数据。可以通过以下方式通信:

  • 使用 multiprocessing.Queue
  • 使用 multiprocessing.Pipe

  • 六、总结

  • IO 密集型任务:使用多线程 (threading 或线程池 ThreadPoolExecutor)。
  • CPU 密集型任务:使用多进程 (multiprocessing 或进程池 ProcessPoolExecutor)。
  • 线程池与进程池:推荐使用 concurrent.futures,自动管理线程/进程,简化代码。
  • 通过合理地使用多线程和多进程,可以大幅提升 Python 程序的性能,特别是在处理大规模数据、网络请求或计算任务时。希望本文的介绍与示例能帮助你更好地理解并发编程并将其应用到实际项目中。

    作者:全栈探索者chen

    物联沃分享整理
    物联沃-IOTWORD物联网 » 并发编程:使用多线程与多进程提升 Python 程序性能

    发表回复