一文讲透“进程、线程、协程”( 四 )


无论是单核还是多核,一个进程永远只能同时执行一个线程(拿到 GIL 的线程才能执行 , 如下图所示) , 这就是为什么在多核CPU上 , Python 的多线程效率并不高的根本原因 。
一文讲透“进程、线程、协程”文章插图
那是不是在Python中遇到并发的需求就使用多进程就万事大吉了呢?其实不然 , 软件工程中有一句名言:没有银弹!
何时用?常见的应用场景不外乎三种:

  • CPU密集型:程序需要占用CPU进行大量的运算和数据处理;
  • I/O密集型:程序中需要频繁的进行I/O操作;例如网络中socket数据传输和读取等;
  • CPU密集+I/O密集:以上两种的结合
CPU密集型的情况可以对比以上multiprocessing和threading的例子 , 多进程的性能 > 多线程的性能 。
下面主要解释一下I/O密集型的情况 。 与I/O设备交互 , 目前最常用的解决方案就是DMA 。
什么是DMADMA(Direct Memory Access)是系统中的一个特殊设备 , 它可以协调完成内存到设备间的数据传输 , 中间过程不需要CPU介入 。
以文件写入为例:
  • 进程p1发出数据写入磁盘文件的请求
  • CPU处理写入请求 , 通过编程告诉DMA引擎数据在内存的位置 , 要写入数据的大小以及目标设备等信息
  • CPU处理其他进程p2的请求 , DMA负责将内存数据写入到设备中
  • DMA完成数据传输 , 中断CPU
  • CPU从p2上下文切换到p1,继续执行p1

一文讲透“进程、线程、协程”文章插图
Python多线程的表现(I/O密集型)
  • 线程Thread0首先执行 , 线程Thread1等待(GIL的存在)
  • Thread0收到I/O请求 , 将请求转发给DMA,DMA执行请求
  • Thread1占用CPU资源 , 继续执行
  • CPU收到DMA的中断请求 , 切换到Thread0继续执行

一文讲透“进程、线程、协程”文章插图
与进程的执行模式相似 , 弥补了GIL带来的不足 , 又由于线程的开销远远小于进程的开销 , 因此 , 在IO密集型场景中 , 多线程的性能更高
实践是检验真理的唯一标准 , 下面将针对I/O密集型场景进行测试 。
测试
  • 执行代码
import multiprocessingimport threadingimport timedef count(num): time.sleep(1) ## 模拟IO操作 print("Process {0} End".format(num))if __name__ == '__main__': start_time = time.time process = list for i in range(5): p = multiprocessing.Process(target=count, args=(i,)) # p = threading.Thread(target=count, args=(i,)) process.append(p) for p in process: p.start for p in process: p.join end_time = time.time print("Total time:{0}".format(end_time - start_time))
  • 结果
## 多进程Process 0 EndProcess 3 EndProcess 4 EndProcess 2 EndProcess 1 EndTotal time:1.383193016052246## 多线程Process 0 EndProcess 4 EndProcess 3 EndProcess 1 EndProcess 2 End