Python 被认为“不是真的多线程”主要是由于全局解释器锁 (GIL) 的存在。
GIL 是 CPython 解释器(Python 最常用的实现)中的一个机制,它限制了同时只能有一个线程执行 Python 字节码。
以下是更详细的解释:
什么是 GIL
全局解释器锁 (GIL) 是一个互斥锁,用于保护访问 Python 对象的全局状态,确保只有一个线程能够执行 Python 字节码。GIL 的存在是为了简化内存管理,使 CPython 的实现变得更简单和高效。
为什么 Python “不是真的多线程”
Python 中的全局解释器锁(GIL)就像是一扇门,只有一把钥匙,只有一个线程能在同一时间进来执行代码。即使你的程序有多个线程,但由于 GIL 的存在,实际上只有一个线程在执行 Python 代码,其他线程必须等待。
这意味着,Python 的多线程在执行 CPU 密集型任务时不会真正提高效率,因为多个线程不能同时“动手”。所以,虽然你可以用多个线程来处理任务,但在实际运算时,它们并不会同时工作。
不过,对于 I/O 密集型任务(例如网络请求或文件读写),多线程可以提升性能,因为当一个线程在等待 I/O 操作时,其他线程可以继续执行。因此,GIL 的影响主要体现在 CPU 密集型任务上,而在处理 I/O 相关的任务时,多线程还是能带来好处的。
GIL 的影响
-
限制 CPU 密集型任务的多线程并行执行:
- 在 CPU 密集型任务中,即使你使用多线程来分割任务,GIL 也会导致只有一个线程能在任意时刻执行 Python 字节码。这样多线程的 CPU 密集型任务并不能真正实现并行计算。
-
I/O 密集型任务中的多线程仍有用:
- 对于 I/O 密集型任务(如网络请求、文件读写),线程在等待 I/O 操作完成时会释放 GIL,从而允许其他线程执行。因此,Python 的多线程在 I/O 密集型任务中仍然能够提高性能。
多线程的替代方案
-
多进程:
- 使用
multiprocessing
模块,Python 可以创建多个进程,每个进程都有自己的 Python 解释器和 GIL,因此可以实现真正的并行计算。
from multiprocessing import Process def worker(num): print(f'Worker: {num}') if __name__ == "__main__": processes = [] for i in range(5): p = Process(target=worker, args=(i,)) processes.append(p) p.start() for p in processes: p.join()
- 使用
-
异步编程:
- 使用
asyncio
模块进行异步编程,可以有效地处理 I/O 密集型任务,通过协程实现并发,而不是并行。
import asyncio async def worker(num): print(f'Worker: {num}') await asyncio.sleep(1) async def main(): tasks = [asyncio.create_task(worker(i)) for i in range(5)] await asyncio.gather(*tasks) asyncio.run(main())
- 使用
-
其他解释器:
- 一些 Python 解释器如 Jython 和 IronPython 没有 GIL,可以实现真正的多线程并行。但是这些解释器的生态系统和性能可能与 CPython 有所不同。
结论
尽管 Python 的 GIL 限制了其在 CPU 密集型任务中的多线程并行能力,但在 I/O 密集型任务中,多线程仍然有用。此外,通过使用多进程或异步编程,可以绕过 GIL 的限制,实现并发或并行计算。