关于GIL的一些理解总结

GIL全局解释器锁(cpython)
一个线程运行python,而其他N个睡眠或者等待I/O
(即保证同一时刻只有一个线程对共享资源进行存取)
Python线程也可以等待threading.lock或者线程模块中的其他同步对象,线程处于这种状态也称之为“睡眠”。

线程如何切换?

协同式多任务处理: 就是线程睡眠或等待网络I/O ,其他线程有机会获取GIL执行代码,礼貌的运行方式,它允许并发,多个线程同时等待不同事件
例如两个线程在同一时刻只能有一个执行python,但一旦线程开始连接,它就会放弃GIL,这样其他线程就可以运行,就意味着可以并发等待套接字连接。

抢占式多任务处理
python2 1000字节码指令检测,定期从他们锁中抽出
python3 运行15毫秒抽出GIL,此时其他线程可以运行

python中的线程安全
多线程在运行时,一个线程随时失去GIL,必须使代码线程安全
虽然python很多操作是原子的
例如在列表中调用sort()就是原子操作,线程在排序期间是不能被打断的, 其他线程进来看不到列表排序的部分,也不会在列表排序前看到过期的数据。
把list一个列表调用 sort排序,因为 自身是单个字节码,因此线程没有机会在调用期间抓取 GIL 。我们可以总结为在 sort() 不需要加锁。
但是 例如 += 不是原子操作会丢失数据,

n = 0

def foo():
    global n
    n += 1
1. 将 n 值加载到堆栈上
2. 将常数 1 加载到堆栈上
3. 将堆栈顶部的两个值相加
4. 将总和存储回 n
(后进先出)
可能发生在加载n值到堆栈,存储回n的时候,可能加到100的时候,只能到98,99.

所以除了GIL还需要加锁保护共享的可变状态。
尽管 GIL 不能免除我们加锁的需要,但它确实意味着没有加细粒度的锁的需要(所谓细粒度是指程序员需要自行加、解锁来保证线程安全,典型代表是 Java , 而 CPthon 中是粗粒度的锁,即语言层面本身维护着一个全局的锁机制,用来保证线程安全)。在线程自由的语言比如 Java,程序员努力在尽可能短的时间内加锁存取共享数据,减轻线程争夺,实现最大并行。然而因为在 Python 中线程无法并行运行,细粒度锁没有任何优势。只要没有线程保持这个锁,比如在睡眠,等待I/O, 或者一些其他失去 GIL 操作,你应该使用尽可能粗粒度的,简单的锁。其他线程无论如何无法并行运行。
并发可以完成更快
这里写图片描述
我敢打赌你真正为的是通过多线程来优化你的程序。通过同时等待许多网络操作,你的任务将更快完成,那么多线程会起到帮助,即使在同一时间只有一个线程可以执行 Python 。这就是并发,线程在这种情况下工作良好。
正如我们所看到的,在 HTTP上面获取一个URL中,这些线程在等待每个套接字操作时放弃 GIL,所以他们比一个线程更快完成工作。

从该文章提炼了下,有兴趣的可以详细阅读下
http://python.jobbole.com/87743/

猜你喜欢

转载自blog.csdn.net/hr504883750/article/details/81809863