多线程作为处理高并发任务的解决方案,是每个开发者都要面对的问题。
保证线程安全是多线程中的重要话题,试想,如果多线程代码跑出来的结果和单线程是不同的,那么这个可不是什么好的事情,通常也就是发生了线程的不安全事件。
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
如果每个线程对变量只有读操作,而无写操作,那么可以说这个线程是安全的;如果有多个线程同时执行写操作,一般都要考虑线程同步,否则可能会影响线程安全。
多线程的物理条件
技术的发展,cpu的制作工艺的提升,现在一般我们使用的笔记本等都是多核。
为了提高效率。可采用的方法有多进程和多线程两种方法,前者不能够共享资源,后者是共享资源。而进程间的通信成为IPC这是另外的话题了,不在我们的讨论之中。
保证线程安全
为了抑制线程间的资源竞争保证数据安全,需要对线程加锁,从而保证同一时刻只有一个线程进行存取。
针对加锁的位置不同,分为两种粒度的锁
1.fine-grained(细粒度),需要开发者自信的加锁,解锁保证线程安全。
2.coarse-grained(粗粒度),语言层面本身维护者一个全局锁的机制。
第一种方式典型的有java,Jython等,后一种方式比较典型的是CPython
python中的GIL
背景:
1.GIL是什么?GIL全程Global Interpreter Lock 全局解释器锁,作为设计之初的考虑,为了数据安全所做的决定。
2.每个CPU在同一时刻只能执行一个线程。单核CPU下的多线程其实都是并发,不是并行,并发和并行从宏观上来说都是同时处理多路请求的概念,但是并发和并行又有区别,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或者多个时间在同一时间间隔内发生
python多线程执行方式
1.获取GIL
2.执行代码知道sleep或者python虚拟机将其挂起
3.释放GIL
某个线程想要执行,必须先拿到GIL,可以看作是通行证,并且在一个进程中只有一个GIL.拿不到通行证的线程,就不允许进入CPU执行
python 2中GIL的释放逻辑是当前线程遇到IO操作或者ticks技术达到100
ticks可以看作是python 自身的计时器,专门作用于GIL,每次释放后归零,这个计数可以通过sys.setcheckinterval来调整。
每次释放GIL锁,线程进行锁竞争,切换线程都会消耗资源。并且由于GIL锁存在,python 一个进程中永远只能同时执行一个线程,就是具备了GIL的那个线程,这就是为什么在多核心CPU上,python的多线程效率并不高
如何选择python 的多线程使用场景
1.在CPU密集型代码(各种循环,计数,判断)在这种场景下,tisck技术很快就会达到阈值,然后触发GIL的释放并引发竞争,所以多线程对CPU密集型代码并不友好。
2.IO密集型代码(文件处理,网络爬虫等),多线程能够有效提升效率(单线程下游IO操作会进行IO等待,造时间的浪费)。所以python的多线程对IO密集型代码比较友好。
在python3中,GIL不使用ticks计数,改为使用计时器,改善了一些,但是依然效率不尽如人意。
多核多线程比单核多线程更差,原因是单核多线程每次释放GIL。唤醒的那个线程都能取得GIL锁,所以能够无缝执行,但是多核下,CPU0释放GIL后,其他CPU上的线程都会竞争,但是GIL可能又会马上被CPU0拿到,导致其他几个CPU上被唤醒后的线程会醒着等待到切换时间后又进入等待调度状态,这样会造成县城颠簸,导致效率更低。
python 为什么选择GIL 还不摒弃
语言的作者龟叔给出了解答
http://www.artima.com/weblogs/viewpost.jsp?thread=214235
他表示,曾尝试去除GIL的限制,但是导致单进程速度变成了2倍。这显然是不可以接受的。
并且python语言本身不存在GIL而只是cpython的解释器受到GIL限制,
同时如果移除了GIL会导致大量的现有的库出现各种各样的问题。(这个算是历史遗留问题吧)