GIL In Python

发布于 2017-10-12 16:46:31

从来没有过在python下的多线程编程经验,对GIL也只是略有耳闻。GIL是“Global Interpreter Lock”全局解释锁的缩写。

搜“Python的多线程上下文如何切换”的时候,在Stackoverflow看到了一个PPT

看了下发现对GIL解释得很清楚、很底层。在此简单写下关键的几点,详情请看源PPT。

  • 问题
    • 把一个依赖CPU的操作(不依赖网络IO)分割到多线程之后,总时间反而增加
      • 单核和多核CPU略有不同
  • 基本结构
    • Python使用的是真正的系统线程(pthreads或windows线程)
    • 这些线程由操作系统来调度
    • GIL禁止了并发执行
    • GIL的程序结构
      • locaked = 0
      • mutex = pthreads_mutex()
      • cond = pthreads_cond()
    • GIL何时释放锁
      • I/O
      • 定时的100"ticks”(可简单地对应到程序语句的条数)
    • GIL锁的获取释放机制
      • 通过cond这个信号来通知另一线程来激活
      • 信号由线程库和操作系统来处理
      • OS的条件变量cond有一个内部的等待队列(FIFO)(wait操作不一定会让最需被调度的线程被调度到)
      • OS有一个有优先级的进程/线程队列,处于等待下次运行的状态中(被信号通知的线程不一定会被系统调度到)
        1. 在多核环境中,可运行的线程在多核中被同时调度,造成了对GIL锁的竞争
        1. 由于系统存在缓冲,IO操作一般马上会被满足而导致线程继续运行,减少了由于IO导致的线程切换的机会
      • 由于上面两点,造成了GIL锁释放获取的不断的“抖动”
  • Python3.2 引入的新的GIL锁
    • 全局的 static volatile int gil_drop_request = 0;
    • 一个线程一直运行直到它被设置为1
    • GIL锁的获取释放机制
      • 主动释放:当前线程主动释放持有的GIL,并通过信号量来通知另外线程(这和前面是一致的)
      • 强制释放:其他进行进行有超时(5ms,可更改)的等待,超时后设置全局量为1;当前线程释放GIL锁,并通知其他线程;其他线程获取到GIL后通过信号量向原线程发送确认
      • 这样避免了GIL的竞争
    • 效果
      • 线程化和顺序执行的运行时间相当,不再有由于锁竞争而导致的更多的时间损耗
      • 并没有加速程序的运行,总时间依然一样
      • 多线程的唤醒并不公平,可能造成某些线程饥饿
      • IO操作总是释放GIL,而不阻塞的IO操作总是重试,造成拖延;而那些有阻塞的线程当数据准备好的时候,却没有得到相应,造成了更多的时间等待
      • 根据54页的实验结果,…
    • 解决办法
      • 引入线程优先级,CPU密集的低优先级,IO密集的高优先级;高优先级的能立即取代低优先级的
      • 奖励主动释放的线程,惩罚被强制释放GIL的线程

为什么需要 GIL

翻到一篇不错的文章,python-multithreading-gil-example.html

Python 是采用引用计数的原理做垃圾回收的,GIL 是为了防止多线程之间对这个计数锁的竞争。

comments powered by Disqus