前言
为了提高程序的运行速度,我们除了修改程序逻辑改善算法,另一个角度就是采取并发的策略。并发有两种,一种是进程的并发,另一种是线程的并发,两者有很多区别,最主要的区别:进程之间是无共享资源的,因而比较耗费内存资源,优点则是更好的安全性;而线程因为共享资源,所以相对于进程更加轻量级,缺点则是对共享数据的访问需要人为管理,即存在“线程安全”问题。
何为线程安全
线程安全是多线程编程里面的独有概念(从名字上也能看出来^_^),下面看一下wikipedia上面的解释:
Thread safety is a computer programming concept applicable in the context of multi-threaded programs. A piece of code is thread-safe if it functions correctly during simultaneous execution by multiple threads. In particular, it must satisfy the need for multiple threads to access the same shared data, and the need for a shared piece of data to be accessed by only one thread at any given time.
简言之,线程安全可以保证在多个线程同时执行的情况下程序可以正确执行,并且对于共享数据在任何时候只能有一个线程进行读取(我们知道产生线程死锁的一个必要条件就是互斥访问,这里面对于共享数据就是互斥访问的)。
如何保证线程安全
保证线程线程安全可以参照进程间对于临界资源的处理方式,加锁是比较简单的做法。为了做到线程安全,不同的编程语言采取不同的加锁方式,大体有两个级别的:细粒度的锁,仅在需要的时候才加,因为编程语言不知道你什么时候需要加锁,所以这个任务就要给编程人员去掌控,java就是采取这种策略,jython(Python 的java实现)也是采用的这个方式;粗粒度的锁,为了绝对的保证线程的安全,采取全局加锁的策略,这个由编程语言直接管理,编程人员不用操心,典型的是CPython(我们说的Python默认就是CPython)。简单的说前一种更安全,后一种更方便,作为一个手懒、饭都懒得吃的人,我比较喜欢后一种方式,把哪些琐碎的事交给编程语言去做吧。
Python的GIL
前面交代过了,正是由于全局的加锁的想法,CPython的作者(当然就是正统的Python的创造者)Guido在Python引入了GIL(Global Interpreter Lock)。每一个interperter进程,任何时候都只能有一个线程来执行,获得锁并使用资源。这种方式避免了多个线程的并发执行,因而保证了线程的安全。但是线程都无法并发执行了,那还叫多线程吗?⊙﹏⊙‖∣
所以对于GIL始终有两种声音:一种可以接受GIL(只是能接受,未必很喜欢),另一种认为应该去掉GIL。认为能接受的原因由两个:一、自从Python出现后,后面太多的模块都使用了Python的GIL来编写,一旦去掉GIL,这些模块都得重写,那些复杂的多线程逻辑得由程序员自己编写,代价很大;二、去掉GIL对于单线程程序会有性能的降低(在1999年就试过,移除了GIL导致单线程执行速度下降了2倍多)。认为该移除的原因主要也有两个:一、去掉GIL才能发挥多核处理器的优势。二、去掉GIL可以加快多线程程序的执行速度,实现真正的多线程。
总结
GIL方案确实不够好(从多线程并发的角度),但是单纯的移除GIL并非完美的对策:不仅把烂摊子给了程序员,而且损失了单线程的运行速度。尤其对于Python,简单和优雅是它一贯的特色,移除GIL会是多线程问题变得复杂,也没了优雅。如果真的对性能要求高,那就用Python的多进程吧,呵呵。