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