1. GIL 是什么?
GIL 全称 Global Interpreter Lock,全局解释器锁。
在 CPython 解释器中,同一时刻通常只允许 一个线程执行 Python 字节码。
也就是说,即使你开启了多个线程:
python
import threading
这些线程在执行 Python 代码时,并不是多个线程真正同时跑 Python 字节码,而是需要轮流拿到 GIL 才能执行。
2. 为什么 Python 要有 GIL?
主要原因是:保证 CPython 内部对象的线程安全。
Python 中很多对象都有引用计数,例如:
python
a = []
b = a
CPython 需要维护对象的引用计数。如果多个线程同时修改引用计数,就可能出错。
为了简单、稳定地保护解释器内部状态,CPython 使用了 GIL。
3. GIL 对爬虫有什么影响?
爬虫大多数场景属于 IO 密集型任务,比如:
- 发 HTTP 请求
- 等服务器响应
- 读取网页内容
- 写入数据库
- 读写文件
这些操作大部分时间都在"等待",不是一直占用 CPU。
所以:
GIL 对普通爬虫影响不大,多线程仍然有效。
例如:
python
import requests
from concurrent.futures import ThreadPoolExecutor
def fetch(url):
r = requests.get(url, timeout=10)
return r.text
urls = ["https://example.com"] * 20
with ThreadPoolExecutor(max_workers=10) as pool:
results = list(pool.map(fetch, urls))
这个场景下,多线程可以提升效率,因为线程在等待网络响应时会释放 GIL,让其他线程继续执行。
4. GIL 对哪些爬虫场景影响较大?
如果爬虫中包含大量 CPU 密集型任务,GIL 影响就比较明显。
比如:
- 大量加解密计算
- 大量 JS 逆向算法计算
- 图片识别
- OCR
- 大规模正则匹配
- HTML 超大文本解析
- 数据清洗计算量很大
- 压缩、解压缩、哈希计算
例如:
python
def calc_sign(data):
# 大量 CPU 计算
pass
这种任务用多线程不一定能提升性能,因为多个线程会争抢 GIL。
5. 如何绕过 GIL?
方案一:使用多进程
多进程是最常见方案。
每个进程都有自己独立的 Python 解释器和 GIL,因此可以真正利用多核 CPU。
python
from multiprocessing import Pool
def parse_html(html):
# CPU密集型解析
return html.count("div")
if __name__ == "__main__":
html_list = ["<div></div>" * 100000] * 8
with Pool(4) as pool:
result = pool.map(parse_html, html_list)
print(result)
适合:
- JS 加密计算
- 大量数据清洗
- OCR
- 复杂解析
方案二:使用 C/C++ 扩展或第三方库
部分底层库会释放 GIL,例如:
- NumPy
- lxml
- pandas 部分操作
- cryptography 部分加密计算
如果核心计算在 C 层完成,可能不会长期占用 GIL。
方案三:把任务拆给外部服务
例如爬虫项目中:
- Python 负责调度和请求
- Node.js 执行 JS 加密逻辑
- Go 服务负责高并发请求
- Java 服务负责复杂业务处理
这种方式适合大型项目。
6. 总结
GIL 是 CPython 中的全局解释器锁,它保证同一时刻只有一个线程执行 Python 字节码。
对爬虫来说,如果是网络请求、数据库读写这类 IO 密集型任务,影响不大,因为线程等待 IO 时会释放 GIL,多线程仍然能提升效率。
但如果爬虫中有大量 CPU 密集型任务,比如 JS 加密计算、图片识别、大规模数据清洗,多线程会受 GIL 限制,无法充分利用多核。
解决方式包括使用多进程、asyncio 异步 IO、C 扩展库,或者将计算任务拆分到 Node、Go 等外部服务中。
感谢关注【遇事不決洛必達】!欢迎点赞收藏和交流指正,我会持续分享我的学习经验和心得。