🧩 第五章 理解内存结构:程序员的基本素养
📄 一句话概括

实测算数任务 4 线程几乎没快(都是 0.03 秒左右),等待任务快了 4 倍(0.8 秒降到 0.2 秒)——线程的价值在【等】上。

📝 本节和前面几节的关系

5.3 讲了虚拟机怎么跑代码,这一节讲同时跑几份是怎么做到的。

一、线程是什么

一个进程里可以有多条执行线,它们共享同一个堆。 共享堆就是共享对象——所以多个线程能同时改同一个对象,这正是麻烦的来源。 每条线程有自己的栈:各自的局部变量、各自的调用链。

二、实测两类任务

算数(吃 CPU):单线程跑 4 份和 4 线程各跑 1 份,都是 0.03 秒左右——几乎没快。 等待(不吃 CPU):单线程 0.8 秒,4 线程 0.2 秒——快了 4 倍。 同样是【4 个线程】,差距却是天壤之别。

三、为什么:GIL

Python 有 GIL(全局解释器锁):同一时刻只有一个线程能执行字节码。 所以吃 CPU 的活儿,多线程只是轮着跑,总时间不变。 等待的活儿不一样:sleep 时线程主动让出 GIL,别人就能跑——真的重叠了。

四、该用线程还是进程

IO 密集(读文件、发请求、等数据库):用线程,等待时可以重叠。 CPU 密集(算、压、加密):用多进程,绕开 GIL 真正并行。 判断标准只有一条:这段代码的时间花在【等】上还是花在【算】上。 线程还带来一个新麻烦:共享堆上的对象谁先改。这是并发最难的题,第 12 章还会提。

五、动手 Demo:CPU 同时运行多个线程

两种任务各跑一遍:一遍单线程做 4 份,一遍 4 线程各做 1 份。 看时间差多少。

import threading, time
def burn(n):
    s = 0
    for i in range(n):
        s += i

# 算数:单线程 4 份 vs 4 线程各 1 份
# 等待:time.sleep(0.2) 各 4 次,同样两种跑法

实跑输出:

--------------------------------------------------------------------
CPU 同时运行多个线程
--------------------------------------------------------------------

  【同时】这个词要看任务类型。**实测两种任务,4 个线程对单线程:**

  任务类型             单线程跑 4 份     4 线程各跑 1 份     快了吗
  -----------------------------------------------------------------------
  算数(吃 CPU)       0.032 秒          0.029 秒            几乎没快
  等待(不吃 CPU)     0.802 秒          0.202 秒            快了 4.0 倍

  **实测结论很清楚:吃 CPU 的活儿,4 个线程和 1 个线程差不多;等待的活儿快 4 倍。**
  原因是 Python 有 **GIL**(全局解释器锁):**同一时刻只有一个线程能执行字节码。**
  **所以线程的价值在【等】上,不在【算】上。**这一条对后面讲服务器并发很关键。

实测:算数任务两种跑法都约 0.03 秒(几乎没快),等待任务从 0.8 秒降到 0.2 秒(快 4 倍)。 原因是 GIL:同一时刻只有一个线程执行字节码——线程的价值在【等】上,不在【算】上。

30 秒实验

  1. 把一段读文件的代码改成多线程,看总时间掉多少。
  2. 把一段纯计算的代码改成多线程,看它是不是白改。

常见误区

  1. 以为多线程一定更快。吃 CPU 的活儿不会快。
  2. 以为线程越多越好。切换本身要花钱,还有共享数据打架。
自测三问(点开对答案) 1. 两类任务的实测结果各是什么? —— 算数两种跑法都约 0.03 秒(几乎没快);等待从 0.8 秒降到 0.2 秒(快 4 倍)。 2. 为什么算数任务多线程不快? —— GIL 让同一时刻只有一个线程执行字节码,多线程只是轮着跑,总时间不变。 3. 什么时候用线程、什么时候用进程? —— IO 密集用线程(等的时候能重叠),CPU 密集用多进程(绕开 GIL 真并行)。

相关笔记