多线程与多进程

  • 进程 (process):具有一定独立功能的程序关于某个数据集合的一次运行活动,是正在运行的程序实例,是系统进行资源分配和调度的独立单位。多个进程间无法共享一个全局变量,数据共享需要其他单独机制实现。凡是用于完成操作系统的各种功能的进程就是系统进程,而所有由你启动的进程都是用户进程;
  • 线程 (thread):操作系统进行运算调度的最小单位,是进程中一个单一顺序控制流。线程作为进程的实际运作单位包含在进程中。一个进程中可以并发多个并行执行的线程完成不同的任务。

多线程 (multithreading) 指一个进程中同时有多个正在执行的线程的状态;多进程 (multiprocessing) 指有多个进程同时执行的状态。

并发与并行

并发 (concurrency):对于单核 CPU,同时最多只能运行一个线程。为了实现多个线程的操作,可以通过分时把单核 CPU 的运行时间以时间段为单位分配给各个线程,使之交替运行。由于线程在 CPU 间的切换耗时可以忽略不计,可认为这些线程是并发运行的。一个线程运行时,其他线程处于挂起状态。
并行 (parallel):对于多核 CPU,可用时执行多个线程的运行方式。

并行在硬件上需要多处理器系统的支持。并发可由操作系统实现而与硬件无关。

Joe Armstrong.jpg

CPU 密集型任务与 I/O 密集型任务

CPU 密集型 (CPU-bound) 任务:CPU 受限型。程序将大部分时间耗费在 CPU 运算操作而不是 I/O 上。CPU 利用率相对较高,运行时间取决于 CPU 性能,表现为任务中有大量计算、编解码等过程;
I/O 密集型 (I/O bound) 任务:I/O 受限型。程序将大多数时间耗费在 I/O 操作而不是运算上,CPU 利用率相对较低,运行时间取决于 I/O 性能,表现为大量的网络通信、磁盘读写等过程。
提升 CPU 密集型任务执行效率的方法是使用多核心 CPU、用更底层的计算机语言编写计算相关逻辑;提升 I/O 密集型任务执行效率的方法是使用多线程等。网络爬虫一般属于 I/O 密集型任务,多线程的应用可以显著提升爬虫的性能。

Python 中的多线程与多进程

出于数据安全的考虑,Python 中设计了 GIL (Global Interpreter Lock, 全局解释器锁) 机制,但由于该机制的限制,使不论是多少 CPU 核心数,同一时刻只能运行一个线程,限制了多核并行的优势。
对于 Python 多线程,各个线程的执行遵循以下步骤:

  1. GIL 的获取;
  2. 执行该线程的代码;
  3. GIL 的释放。

因此,多线程的关键就是要获取到作为多线程“凭证”的 GIL。但即使是在多核心 CPU 的条件下,各进程在同一时刻也只存在一个运行着的线程。而对于多进程而言,各个进程彼此独立拥有各自的 GIL,不受 GIL 的影响。可见,多进程能更好地发挥多核优势。

对于 CPU 密集型任务,由于 GIL 的存在,在多核 CPU 的多线程效率可能比单核的更低。所幸,Python 的多进程比多线程更能带来运行效率的飞跃。 网络爬虫是 I/O 密集型任务,多线程或多进程的差别不显著,但剂量使用多进程。