线程与 GIL
理解 Python 线程的限制
线程与 GIL 是 CoddyKit 上的免费 Python Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Python Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Python Academy 课程共包含 4 节课。
什么是线程?
线程是一个进程内部独立的执行流。Python 的 threading 模块可以让您在一个程序中并发运行多个函数。
import threading
def worker():
print('hello from thread')
t = threading.Thread(target=worker)
t.start()
t.join()
print('main done')启动多个线程
您可以启动多个线程。每个线程都会运行其目标函数。join() 会让主线程等待某个线程完成。
import threading
def task(name):
print('task', name)
threads = [threading.Thread(target=task, args=(i,)) for i in range(3)]
for t in threads:
t.start()
for t in threads:
t.join()
print('all finished')全局解释器锁
CPython 具有全局解释器锁(GIL):同一时刻只有一个线程执行 Python 字节码。这意味着对于纯 Python 的 CPU 工作,线程无法带来真正的并行加速。
import sys
print('Running on:', sys.implementation.name)
print('Only one thread runs Python bytecode at a time due to the GIL')线程仍然有用的场景
线程非常适合I/O 密集型工作:网络请求、磁盘读取和等待。当一个线程等待时,GIL 会被释放,另一个线程便可以运行。
import threading, time
def fake_io(name):
time.sleep(0.1)
print('done', name)
start = time.perf_counter()
ts = [threading.Thread(target=fake_io, args=(i,)) for i in range(3)]
for t in ts: t.start()
for t in ts: t.join()
print('elapsed about 0.1s, not 0.3s')CPU 密集型工作不会加速
对于繁重的计算,添加线程不会让程序更快,因为 GIL 会将它们串行化。对于 CPU 工作,请改用 multiprocessing。
import threading
def burn():
total = 0
for i in range(1000000):
total += i
return total
t = threading.Thread(target=burn)
t.start()
t.join()
print('CPU-bound: threads share one core under the GIL')守护线程
主程序退出时,守护线程会自动终止。如果您不需要等待某个后台辅助线程完成,可以使用守护线程。
import threading, time
def background():
time.sleep(0.05)
print('background tick')
t = threading.Thread(target=background, daemon=True)
t.start()
t.join()
print('main exits')命名和识别线程
每个线程都有名称和 ID。threading.current_thread() 会返回当前运行的线程对象,便于记录日志。
import threading
def show():
t = threading.current_thread()
print('running in', t.name)
t = threading.Thread(target=show, name='Worker-1')
t.start()
t.join()
print('main thread:', threading.current_thread().name)继承 Thread
除了传入目标函数,您还可以继承 Thread 并重写 run()。当线程需要维护自己的状态时,这种方式很有用。
import threading
class Greeter(threading.Thread):
def __init__(self, name):
super().__init__()
self.who = name
def run(self):
print('hi', self.who)
g = Greeter('Sam')
g.start()
g.join()从线程返回结果
目标函数的返回值会被丢弃。要收集结果,请将它们写入共享结构中(稍后您会看到队列等更安全的工具)。
import threading
results = {}
def compute(key):
results[key] = key * key
ts = [threading.Thread(target=compute, args=(i,)) for i in range(4)]
for t in ts: t.start()
for t in ts: t.join()
print(results)统计活动线程
threading.active_count() 会告诉您当前存活的线程数,其中包括主线程。
import threading, time
def wait():
time.sleep(0.05)
ts = [threading.Thread(target=wait) for _ in range(2)]
for t in ts: t.start()
print('active now:', threading.active_count())
for t in ts: t.join()
print('active after join:', threading.active_count())选择线程还是进程
经验法则是:使用线程处理 I/O 密集型并发,使用进程实现 CPU 密集型并行。原因就在于 GIL。
tasks = {'download files': 'threads', 'crunch numbers': 'processes', 'call APIs': 'threads'}
for task, choice in tasks.items():
print(task, '->', choice)快速检查
检验您对线程和 GIL 的理解。
回顾
您学习了线程和 GIL:
- 线程通过
threading.Thread在一个进程内部并发运行。 - GIL 会将 Python 字节码的执行串行化。
- 线程适合 I/O 密集型工作;进程适合 CPU 密集型工作。
- 请使用
start()、join()、守护线程和继承来管理线程。
接下来:使用 ThreadPoolExecutor 规范地管理线程。
常见问题解答
「线程与 GIL」课时是免费的吗?
是的 — 「线程与 GIL」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Python Academy 课程的其余内容,请升级到 CoddyKit PRO。 Python Academy 课程共包含 4 节课。
「线程与 GIL」这节课中我会学到什么?
理解 Python 线程的限制 你通过在浏览器中直接运行的动手代码来练习 Python Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Python Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Python Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「线程与 GIL」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Python Academy 课中编写并运行代码吗?
能。每节 Python Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。