Python-线程安全问题
首先先看下面两个例子。
用例1
(数据正确):
import threading
from time import sleep
ticket = 1000
def run():
global ticket
for i in range(100):
ticket -= 1
sleep(0.01)
if __name__ == '__main__':
t1 = threading.Thread(target=run, name='t1')
t2 = threading.Thread(target=run, name='t2')
t3 = threading.Thread(target=run, name='t3')
t4 = threading.Thread(target=run, name='t4')
t1.start()
t2.start()
t3.start()
t4.start()
t1.join()
t2.join()
t3.join()
t4.join()
print(ticket)
输出:
600
用例2
(数据错误):
import threading
n = 0
def run1():
global n
for i in range(1000000):
n += 1
def run2():
global n
for i in range(1000000):
n += 1
if __name__ == '__main__':
t1 = threading.Thread(target=run1, name='t1')
t2 = threading.Thread(target=run2, name='t2')
t1.start()
t2.start()
t1.join()
t2.join()
print('最后打印n:',n)
输出:
最后打印n: 1485696
并发和并行
在进行GIL讲解之前,我们可以先回顾一下并行和并发的区别:并行
:多个CPU同时执行多个任务,就好像有两个程序,这两个程序是真的在两个不同的CPU内同时被执行。
并发
:CPU交替处理多个任务,还是有两个程序,但是只有一个CPU,会交替处理这两个程序,而不是同时执行,只不过因为CPU执行的速度过快,而会使得人们感到是在“同时”执行,执行的先后取决于各个程序对于时间片资源的争夺。
并行和并发同属于多任务,目的是要提高CPU的使用效率。这里需要注意的是,一个CPU永远不可能实现并行,即一个CPU不能同时运行多个程序,但是可以在随机分配的时间片内交替执行(并发),就好像一个人不能同时看两本书,但是却能够先看第一本书半分钟,再看第二本书半分钟,这样来回切换。
进程和线程
接下来我们来看看进程和线程之间的区别:
两个多线程
同时执行死循环,查看单个CPU
的使用率:
两个多线程
同时执行死循环,查看两个CPU
的使用率:
两个多进程
同时执行死循环,查看两个CPU
使用率:
也就是说,多线程
并不会充分调用两个CPU,而是会像在一个CPU上充分运转,而多进程
则是会完全调用两个CPU,同时执行;
GIL-全局解释器锁
为什么会出现上述数据不一致的现象?因为GIL
的存在。
全局解释器所
(global interpreter lock),每个线程在执行时候都需要先获取GIL,保证同一时刻只有一个线程可以执行代码,即同一时刻只有一个线程使用CPU,也就是说python的多线程并不是真正意义上的同时执行。
GIL不是python的特性,为什么会存在GIL?
- Guido van Rossum(吉多·范罗苏姆)创建python时就只考虑到单核cpu,解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁, 于是有了GIL这把超级大锁。因为cpython解析只允许拥有GIL全局解析器锁才能运行程序,这样就保证了保证同一个时刻只允许一个线程可以使用cpu。由于大量的程序开发者接收了这套机制,现在代码量越来越多,已经不容易通过c代码去解决这个问题。
问题1: 什么时候会释放Gil锁?
- 遇到像 i/o操作这种 会有时间空闲情况 造成cpu闲置的情况会释放Gil
- 会有一个专门
ticks
进行计数,一旦ticks数值达到100 这个时候释放Gil锁 线程之间开始竞争Gil锁(说明:ticks这个数值可以进行设置来延长或者缩减获得Gil锁的线程使用cpu的时间) ------这也是上述代码出现问题的原因!!!!!
那么,我们改如何解决GIL锁的问题呢?
1.更换cpython为jpython
(不建议)
2.使用多进程
完成多线程的任务
3.在使用多线程可以使用c语言
去实现
参考资料:python面试不得不知道的点——GIL