为什么Java虚拟机中没有全局解释器锁(GIL)?为什么Python如此迫切需要一个?
在这篇博文的评论中,有一个评论提到了为什么IronPython或Jython很容易摆脱GIL的原因,这是因为CPython使用引用计数,而其他两个虚拟机使用垃圾回收器。具体的机制我不太清楚,但这听起来像是一个合理的原因。
当你在线程之间不加节制地共享对象时,确定没有任何引用指向特定对象的时间是相对困难的。使用全局锁的引用计数是一种(昂贵的)方法。解决这个问题的另一种方式是只允许一个线程同时持有对象的引用,这将使大部分活动在线程本地进行,但会增加线程间通信的难度。个人认为,高性能计算使用处理器间的消息传递而不是共享内存,这是有意义的,并且出于可伸缩性的原因...
嗯,基本上,有两种类型的垃圾回收:标记和清除(或者叫停止和复制,或者你的具体实现如何称呼)。JVM目前使用了两者的组合(代际垃圾回收)。因此,Java应该遇到与Python同步引用计数器的相同问题吗?
解决这个问题的方法是使用垃圾回收器来管理内存,而不是依赖引用计数。垃圾回收器可以跟踪和管理对象的引用,当没有引用指向一个对象时,垃圾回收器会自动释放该对象的内存。这样可以避免多线程环境下的引用计数同步问题,提高程序的并发性能。
以下是Java中使用垃圾回收器的示例代码:
public class MyClass { private Object myObject; public void setMyObject(Object object) { this.myObject = object; } public Object getMyObject() { return myObject; } public static void main(String[] args) { MyClass obj1 = new MyClass(); MyClass obj2 = new MyClass(); obj1.setMyObject(obj2); obj2.setMyObject(obj1); // Both obj1 and obj2 have references to each other // However, since there are no other references to obj1 and obj2 outside of this scope, // the garbage collector can determine that they are no longer needed and free their memory obj1 = null; obj2 = null; // At this point, the garbage collector can safely collect obj1 and obj2 } }
通过使用垃圾回收器,Java可以更好地管理对象的引用和内存,避免了Python中的GIL问题。
JVM(至少是HotSpot)的确有类似于"全局解释锁(GIL)"的概念,只是锁的粒度更细,这主要归功于HotSpot中更先进的垃圾回收机制。
在CPython中,GIL是一个大锁(虽然可能不完全准确,但为了争论的方便可以这么说),而在JVM中,锁更加分散,具体取决于使用的地方。
例如,可以看一下HotSpot代码中的vm/runtime/safepoint.hpp,这是一个有效的屏障。一旦到达安全点,整个VM就会停止执行Java代码,就像Python VM在GIL上停止执行一样。
在Java世界中,这样的VM暂停事件被称为"停止整个世界",在这些点上,只有符合特定条件的本地代码可以自由运行,而其他部分的VM都已经停止。
此外,Java中缺乏粗粒度锁使得JNI编写更加困难,因为JVM对FFI调用的环境提供的保证较少,而这是CPython相对较容易实现的一件事情(虽然不如使用ctypes容易)。
以下是Java没有GIL的原因以及Python为什么如此需要GIL的解决方法:
在Java中,没有GIL的主要原因是JVM使用了更先进的垃圾回收机制和锁粒度更细的机制。这使得在Java中能够更好地支持多线程并发执行。
垃圾回收机制是Java中的一个重要特性,它可以自动管理内存的分配和释放。JVM的垃圾回收器可以在后台运行,不影响Java代码的执行。而CPython中的垃圾回收机制相对较简单,需要使用GIL来保证在垃圾回收期间不会发生并发访问的问题。
另外,JVM中的锁粒度更细,可以更好地支持多线程并发执行。在Java中,锁可以应用于不同的对象和代码块,使得不同线程可以同时执行不同的代码段。而在CPython中,由于GIL的存在,同一时刻只能有一个线程执行Python代码,这导致了Python中多线程程序的性能问题。
为了解决Python中GIL带来的性能问题,可以使用一些解决方案。例如,可以使用多进程代替多线程,每个进程都有自己的解释器和GIL,可以并行执行Python代码。另外,可以使用C扩展来替代Python代码的关键部分,因为C扩展不受GIL的限制,可以实现真正的并行执行。
总结一下,Java没有GIL的原因是JVM使用了更先进的垃圾回收机制和锁粒度更细的机制。而Python需要GIL的原因是CPython的垃圾回收机制相对简单,需要使用GIL来保证并发访问的安全性。为了解决GIL带来的性能问题,可以使用多进程替代多线程,或者使用C扩展来实现并行执行。
为什么Java虚拟机中没有GIL?为什么Python非常需要GIL?
Python语言本身不需要GIL,这就是为什么它可以完美地在Java虚拟机(Jython)和.NET平台(IronPython)上实现,并且这些实现可以进行多线程操作。然而,CPython作为一种流行的Python实现,一直使用GIL来简化编码(特别是垃圾回收机制的编码)以及集成非线程安全的C编码库(以前有很多这样的库存在)。
Unladen Swallow项目是一个野心勃勃的项目,计划为Python开发一个无GIL的虚拟机。他们打算通过实现一种更复杂的垃圾回收系统来实现这一目标,类似于IBM的Recycler(Bacon等人,2001)。
老实说,Bartosz,关于去除GIL的旧尝试,确实存在很多开销(我记得是一个因子2)。1999年,Greg Stein对此进行了测量。基于引用计数的垃圾回收是主要问题,强制进行细粒度锁定,导致巨大的开销。因此,一种更先进的垃圾回收系统对于解决这个问题至关重要。
Unladen Swallow团队已经放弃了去除GIL的计划。
除了Unladen Swallow和CPython之外,还有PyPy、Jython和IronPython。后两者没有GIL,但使用multiprocessing模块可以绕过GIL,并且更安全。
这些替代方案提供了解决GIL问题的方法。