Python并发秘籍:如何一天内让你的程序性能提升100倍?

126 阅读5分钟

在Python中,我们可以用进程协程实现同时做多件事情,就像你一边刷抖音,一边听音乐。 它们适用于不同的场景。

什么是进程?

你可以把进程想象成一家独立的工厂,它有自己的厂房(内存),自己的工人(资源)。如果一个工厂倒闭了,不会影响其他工厂。

  • 特点
    • 独立性强:每个进程都有自己独立的内存空间,互不干扰。 例如,你在两个不同的浏览器窗口打开网页,每个窗口就是一个独立的进程。
    • 资源占用多:因为要独立的内存空间,所以开销比较大。 启动一个进程通常比启动一个线程慢10-100倍。
    • 切换慢:就像你从一个工厂跑到另一个工厂,比较费时间。 进程切换的开销通常在微秒级别。
    • 稳定性高:一个进程崩溃了,不会影响其他进程。 例如,一个浏览器窗口崩溃了,不会导致整个浏览器关闭。
  • 适用场景
    • CPU密集型任务:需要大量计算的任务,比如视频编码、数据分析。 这类任务需要充分利用多核CPU的优势,每个进程在一个CPU核心上跑,速度非常快。
    • 例子:你要用Python压缩一个很大的视频文件,可以把视频分成几段,每个段用一个进程来处理,这样可以充分利用电脑的多个CPU核心,比如4核CPU,理论上可以提速4倍。
    • 代码示例
      from multiprocessing import Process
      import time, os
      
      def task(name):
          print(f'进程 {name} (PID: {os.getpid()}) 开始运行...')
          time.sleep(2)  # 模拟耗时操作,比如复杂的数学计算
          print(f'进程 {name} (PID: {os.getpid()}) 运行结束.')
      
      if __name__ == '__main__':
          processes = []
          for i in range(3):  # 创建3个进程
              p = Process(target=task, args=(f'任务{i+1}',))
              processes.append(p)
              p.start()
      
          for p in processes:
              p.join()  # 等待所有子进程结束
      
          print('所有进程运行完毕!')
      
      这段代码创建了3个独立的进程,每个进程执行task函数。time.sleep(2) 模拟了CPU密集型任务,例如复杂的数学计算。每个进程都在独立的CPU核心上运行,互不干扰。 运行结果显示每个进程的PID(进程ID),并且它们并发执行,总耗时接近2秒,而不是串行执行的6秒。

什么是协程?

协程就像一个工厂里的多个工人,他们共享同一个工厂的地盘(内存),但是他们很自觉,会主动让出时间给别人,避免一个人长时间占用资源。 协程是一种用户态的轻量级线程,由程序员自己调度。

  • 特点
    • 轻量级:协程占用的资源很少,创建和切换速度非常快。 创建一个协程的开销通常比创建一个线程小100倍。
    • 共享内存:所有协程共享同一个进程的内存空间,通信很方便。 但这也意味着需要注意线程安全问题,例如使用锁来避免数据竞争。
    • 切换快:就像工厂里的工人换班,几乎不花时间。 协程切换的开销通常在纳秒级别。
  • 适用场景
    • I/O密集型任务:需要频繁等待的任务,比如网络请求、读写文件。 这类任务CPU并不需要一直忙碌,大部分时间都在等待数据。协程可以在等待的时候切换到其他任务,提高效率。
    • 例子:你写一个爬虫程序,需要从很多网站抓取数据。用协程可以让你在等待网站响应的时候,去抓取其他网站,而不用傻傻地等着。 如果用多线程,当线程数过多时,线程切换的开销会超过实际的抓取时间,导致效率下降。
    • 代码示例
      import asyncio
      import time
      
      async def fetch_data(url):
          print(f"开始获取 {url}")
          # 模拟网络请求的延迟
          await asyncio.sleep(1)  # 模拟网络IO等待
          print(f"{url} 获取完成")
          return f"来自 {url} 的数据"
      
      async def main():
          urls = [
              "https://www.example.com/page1",
              "https://www.example.com/page2",
              "https://www.example.com/page3",
          ]
      
          tasks = [fetch_data(url) for url in urls]
          results = await asyncio.gather(*tasks)
      
          for result in results:
              print(result)
      
      if __name__ == "__main__":
          start_time = time.time()
          asyncio.run(main())
          end_time = time.time()
          print(f"总耗时: {end_time - start_time:.2f} 秒")
      
      这段代码使用了asyncio库来创建和管理协程。async def fetch_data(url) 定义了一个协程函数,模拟了网络请求的延迟。await asyncio.sleep(1) 模拟了I/O等待,例如等待网络响应。 asyncio.gather(*tasks) 并发执行多个协程任务,总耗时接近1秒,而不是串行执行的3秒。

进程 vs 协程:对比表格

特性进程协程
资源占用
并行性真正的并行(多核CPU)单线程并发(看似同时进行)
切换慢 (微秒级)快 (纳秒级)
稳定性相对较低 (需要注意线程安全)
通信进程间通信(IPC)共享内存
适用场景CPU密集型I/O密集型
典型应用大规模并行计算高并发网络服务

总结

  • 如果你的任务需要大量计算,而且你的电脑是多核CPU,那就用进程。 例如,科学计算、视频编码。
  • 如果你的任务需要频繁等待,比如网络请求,那就用协程。 例如,Web服务器、网络爬虫。

选择合适的工具,才能事半功倍! 进程适用于CPU密集型任务,可以充分利用多核CPU的优势;协程适用于I/O密集型任务,可以在等待I/O时切换到其他任务,提高效率。