首先,语言应该在什么级别支持多线程。C 通过操作系统的 preemptive scheduler 支持多线程,同时提供 critical section, wait/notify 这样的同步机制。问题在于,这样的同步机制太低级,在实际应用中经常需要封装为高级的同步机制,比如多线程的生产者-消费者队列。高级动态语言的设计者面临三个选择:
- 在语言中直接提供类似 C 的机制(Java);
- 设计良好的 C 接口,在同一进程中运行多个虚拟机,利用 C 把低级同步机制封装成多虚拟机之间的高级同步机制(Lua),或者把多线程优化完全封装在一个 API 之内(比如 Intel MKL 的各种多核算法)。
- 多进程。
方法 1 是得不偿失的。因为低级同步机制的优势在于效率,而考虑效率就必须考虑诸如 L1/L2/L3 cache 实效之类的底层情况。这对于解释执行的语言(即使有 JIT)来说是无法控制的。所以高级动态语言提供底层同步机制实际没有必要。应该多走方法 2 和 3。其中又以 2 最为灵活。
移除 GIL 难吗?不难,
因为根本没有必要移除。如果 Python 能在一个进程中初始化多个 VM,同时其标准库在 C 级别做出足够多的常用多核优化就没有问题。Python 其实是希望实现另一个功能,异步操作。尽管异步操作和并行计算都可以通过多线程来完成,但是其实前者更加适合用协程或者用户级线程来完成。但是 Python 是 stackful 实现,也就是 byte code 借用 C runtime stack 来维护自己的运行状态。这种机制的弱点就是不容易用跨平台的方式来实现协程,所以利用 OS 多线程加 GIL 也就成了模拟协程语意的妥协手段。
补充一下。C 的线程和线程同步机制虽然底层,但是只有这种机制才能覆盖所有 use case。而其它的高层抽象只能适合某种 case。所以,当我说高级动态语言需要高级的线程操作时,我的隐含意思是这种语言同时还要有和 C 进行良好的互操作来随时扩展这种抽象。
Ruby也有GIL,其实GIL并不是性能问题的根源,性能问题的根源是GC。
假设去掉GIL,像Java那样的多核多线程,你会面临更多头疼的OOM问题,以及GC问题,Java的一次Full GC是stop whole world的,你不希望你整个服务器8颗内核一起stop,等待GC完成吧?
所以Java这么多年疯狂投入努力改进VM的GC效率,搞出来各种GC算法,特别是并行GC算法。而且在大内存服务器上,为了提高GC效率,避免过大的内存堆扫描开销,Java现在也强调单机跑多进程呢。
所以你认为Python/Ruby去掉GIL,就解决问题了吗?事实上会引入更多更麻烦的问题。
如果为了提高IO并发性能,用协程就同样可以达到目的,Ruby现在原生支持协程,Lua则是协程方面效率极高的脚本语言,已经证明了这条路。
最后说到多核的问题,Ruby未来发展方向是MVM,即单进程里面跑多个VM,每个CPU内核跑一个VM,每个VM有自己的GC。这个办法其实很不错,既有效利用了进程共享内存,又可以支持多核并行,还解决了全局GC的性能问题。