1. 并发编程概述:进程与线程的概念、并发与并行的区别、Python中的GIL全局解释器锁
大家好,我是你们这门课的主讲。今天咱们聊聊并发编程最基础、也最绕不开的几个概念。说实话,我刚开始接触这块时,也被进程、线程、并发、并行这些词搞得晕头转向。别急,咱们一个一个掰开揉碎了讲。
1.1 进程与线程:到底是个啥?
先说说进程。你可以把进程想象成一个正在运行的程序的"独立王国"。每个进程都有自己的内存空间、资源句柄,互不干扰。我举个例子:你打开一个浏览器,再打开一个记事本,这就是两个进程。它们各自为政,一个崩了,另一个还能活得好好的。
那线程呢?线程是进程里的"干活的人"。一个进程可以有一个或多个线程,它们共享进程的内存和资源。说白了,线程就是进程里的"轻量级执行单元"。
核心区别一句话总结:
- 进程:资源分配的最小单位,独立内存空间
- 线程:CPU调度的最小单位,共享进程内存
我在项目中遇到过一个问题:用多进程处理大量数据,结果内存直接爆了。后来换成多线程,内存占用降了80%。嗯,这就是共享内存的好处。但要注意,共享也意味着要处理同步问题,这个后面会细讲。
1.2 并发与并行:别搞混了
这两个词,我见过太多人混用了。包括我自己刚入行时也犯过这错。
并发(Concurrency):是逻辑上的"同时"。一个CPU快速切换任务,看起来像同时执行。就像你一边写代码一边听歌,其实是在快速切换注意力。
并行(Parallelism):是物理上的"同时"。多个CPU核心真正同时执行多个任务。就像你左手画圆右手画方,前提是你有两只手。
| 对比项 | 并发 | 并行 |
|---|---|---|
| 本质 | 任务交替执行 | 任务同时执行 |
| 硬件要求 | 单核即可 | 多核必需 |
| 关注点 | 任务调度 | 任务执行 |
| 类比 | 一个人吃三个馒头 | 三个人各吃一个馒头 |
你想想看,如果你的程序是I/O密集型的(比如网络请求、文件读写),并发就够了。但如果是CPU密集型的(比如视频编码、科学计算),那就得上并行。
1.3 Python的GIL:绕不开的"紧箍咒"
好,重点来了。GIL,全称Global Interpreter Lock,全局解释器锁。这是Python里最让人又爱又恨的东西。
GIL是什么? 简单说,它是一把大锁,保证同一时刻只有一个线程在执行Python字节码。你可能会问:那多线程还有啥用?
注意:GIL只影响CPU密集型任务。对于I/O密集型任务,多线程依然能提升性能,因为线程在等待I/O时会释放GIL。
我曾经在一个爬虫项目里踩过这个坑。用多线程爬取100个网页,速度确实快了。但换成多线程做图像处理,反而比单线程还慢。为什么会这样?因为GIL让多个CPU核心无法同时执行Python代码,线程切换反而增加了开销。
来看个代码示例,感受一下GIL的影响:
import threading
import time
def count_down(n):
while n > 0:
n -= 1
# 单线程
start = time.time()
count_down(50000000)
count_down(50000000)
print(f"单线程耗时: {time.time() - start:.2f}秒")
# 多线程
start = time.time()
t1 = threading.Thread(target=count_down, args=(50000000,))
t2 = threading.Thread(target=count_down, args=(50000000,))
t1.start()
t2.start()
t1.join()
t2.join()
print(f"多线程耗时: {time.time() - start:.2f}秒")
运行结果会让你大跌眼镜:多线程版本可能比单线程还慢。这就是GIL的"功劳"。
避坑指南:
- CPU密集型任务:用多进程(multiprocessing)绕过GIL
- I/O密集型任务:用多线程(threading)或异步(asyncio)
- 混合型任务:考虑用concurrent.futures模块
我个人习惯是:先判断任务类型,再选方案。别一上来就无脑上多线程,那可能会让你怀疑人生。
嗯,这里要注意一点:GIL是CPython解释器的实现细节,不是Python语言本身的限制。像Jython、IronPython就没有GIL。不过咱们日常用的基本都是CPython,所以还是得正视它。
好了,这一章的内容就到这里。进程与线程的概念、并发与并行的区别、GIL的影响,这些都是后续章节的基础。下一章咱们会深入聊聊Python的threading模块,看看怎么在实际项目中用好它。