UnderstandingGIL
个人笔记,有一点点修改,不喜右上角。

理解python的GIL

  • 介绍
    • 一个实验
    • 结果
  • 线程与GIL
    • Python Threads
    • 线程执行模型
    • cpu密集型任务
    • 什么是tick
    • 定期check
    • C上的实现
    • 问题
  • GIL和线程切换解构
    • Python Locks
    • 解构Locks
    • 线程间的切换
    • pthread 秘密
    • 操作系统调度
    • 线程间的切换(二)
  • What Can Go Wrong?
    • GIL仪表
    • GIL logging
    • 一个简单的跟踪
    • 一个cpu线程
    • 多核GIL战争
    • 多核事件处理
    • I/O处理的行为
  • 优化过的GIL
    • 新的GIL
    • 新的线程切换
    • 关于新的GIL插图
    • 关于新的GIL插图(二)
    • 关于新的GIL插图(三)
    • 关于新的GIL插图(四)
    • 这个改进有用吗?
  • Die GIL Die!!!
    • 然而这并没有什么卵用
    • 响应时间
    • 不公平的唤醒/饥饿
    • convoy effect(FCFS算法)
    • 一个测试

介绍

  • Cpython有一个全局解释锁Global Interpreter Lock (GIL)

一个实验

  • 思考以下这个简单的CPU绑定功能

    def countdown(n):while n > 0:n -= 1
    
  • 运行一个很大的任务
    import timestart_time = time.time()
    COUNT = 100000000 # 100 million
    countdown(COUNT)
    end_time = time.time()
    print(end_time - start_time)
    
  • 将工作分为两个线程
    from threading import Threadstart_time = time.time()
    t1 = Thread(target=countdown,args=(COUNT//2,))
    t2 = Thread(target=countdown,args=(COUNT//2,))
    t1.start(); t2.start()
    t1.join(); t2.join()
    end_time = time.time()
    print(end_time - start_time)
    

结果

  • 四核MacPro的运行结果

    单线程                       : 7.8s
    多线程(2个线程)               : 15.4s
    多线程(4个线程)               : 15.7s
    多线程(只启用一个cpu 2个线程)  : 11.3s
    多线程(只启用一个cpu 4个线程)  : 11.6s
    

线程与GIL

Python Threads

  • python的多线程是真正的多线程

    • POSIX threads(pthreads)
    • Windows threads
  • 这些线程完全由操作系统管理
  • 在cpython中由python解释器进程执行

线程执行模型

  • 使用gil可以获得协作式多任务处理

  • 当一个线程运行时占有GIL

  • GIL释放在 I/O 等待上 (read,write,send,recv等)

cpu密集型任务

  • 从不执行I/O操作的CPU密集型线程作为特殊情况处理。

  • 每当100次tick出现后check一次

  • 可以使用sys.setcheckinterval来修改

什么是tick

  • tick++松散++地映射到解释器指令

定期check

  • 当前正在运行的线程

    • 重置tick计数器
    • 主线程运行一个信号处理程序
    • 释放GIL
    • 重新获取GIL

C上的实现

/*python/ceval.c*//*Decrement(递减) ticks*/
if (--_Py_Ticker < 0){/*reset ticks*/_Py_Ticker = _Py_CheckInterval;/*run signal handlers*/if (things_to_do){if (Py_MakePendingCalls() < 0){...}}if (interpreter_lock){/*give another thread a chance*/PyThread_release_lock(interpreter_lock);/*Other threads may run now*/PyThread_acquire_lock(interpreter_lock, 1)}
}

问题

  • cpu密集型线程性能损失的来源是什么?
  • GIL的获取以及释放是否由它自身负责?

GIL和线程切换解构

Python Locks

  • cPython解释器仅提供一个锁类型,用于构建所有其他线程同步原语(用于实现线程同步)。
  • 它不是一个简单的互斥锁。
  • 它是由pthreads互斥锁和条件变量构造的二进制信号量。
  • GIL是这个锁的一个实例。

解构Locks

  • Locks 包含三个部分

    locked = 0 # Lock status
    mutex = pthreads_mutex() # Lock for the status
    cond = pthreads_cond() # Used for waiting/wakeup
    
  • acquire和release的工作方式

  • 关键方面涉及线程之间的这种信令

线程间的切换

  • 假设有两个线程,一个在执行,另一个在等待gil

  • 线程1可能堵塞所以它释放了GIL。

  • GIL的释放引起信号通信。

  • 由线程库和操作系统处理。

  • 另一个例子: 当线程1运行到到check:

  • 哪个程序会被运行呢?

pthread 秘密

  • 条件变量有一个内部等待队列。

  • 信号从队列中弹出一个线程。

  • 接下来发生什么

操作系统调度

  • 操作系统有准备运行的线程/进程的优先级队列。
  • 信号(signaled)线程只需进入该队列即可。
  • 然后,操作系统以最高优先级运行进程或线程。
  • 它可能是也可能不是信号(signaled)线程。

线程间的切换(二)

  • 线程1可能继续运行

  • 线程2被移动到os的队列中,等到后面某一时刻执行。

  • 线程2也可能立马执行:

  • 不管怎么都要看优先级。

What Can Go Wrong?

GIL仪表

  • 为了更详细地研究线程间的调度,我使用一些日志记录对Python进行了检测。
  • 记录了GIL释放,获取,冲突,重试等行为的记录。

GIL logging

  • 加入额外的tick计数器以记录检查间隔的循环数
  • 锁定已修改为记录GIL事件(伪代码)

一个简单的跟踪

一个cpu线程

  • 线程交替执行,但切换频率远低于你的想象:

  • 在线程上下文切换之前可能会发生数百到数千次检查(这很好)

多核GIL战争

  • 使用多个cpu,可运行的线程可以同时(在不同的cpu上)进行调度,并通过GIL进行争夺。
  • 线程2不断地接受信号,但当它醒来时GIL已经被其他线程取走了。

多核事件处理

  • cpu密集型线程对于想要处理事件的其他线程来说,难以获取GIL。

I/O处理的行为

  • I/O操作一般不会阻塞。
  • 由于缓存,操作系统能够很快满足I/O请求并保持线程运行。
  • 然而,GIL经常被释放
  • 导致GIL在重载下发生颠簸。

优化过的GIL

新的GIL

  • Python 3.2有一个新的GIL实现
  • 由Antoine Pitrou提出。
  • 为了解决上文提到的GIL thrashing(颠簸)问题。

新的线程切换

  • 现在用一个新的全局变量替代ticks
/* Python/ceval.c */
...
static volatile int gil_drop_request = 0;
  • 线程一直运行直到这个值被设置成1
  • 此时,线程必须丢弃GIL

关于新的GIL插图

  • 假设有个新的线程出现
  • 它处于暂停状态因为GIL被占用。
  • 等待的线程在GIL上执行定时cv_wait函数。
  • TIME_OUT默认时间为5毫秒,可以被更改。
  • 线程2等待查看GIL是否由线程1自动释放(例如,如果有I/O或由于某种原因它进入休眠状态)。
  • 线程1释放GIL。
  • 这是一个简单的例子,第二个线程被信号通知后获取到GIL。

关于新的GIL插图(二)

  • 如果超时了,则将gil_drop_request设置成1。
  • 线程2继续它在GIL上的等待。

关于新的GIL插图(三)

  • 线程1在当前指令后暂停。
  • 发送信号以指示GIL的释放。

关于新的GIL插图(四)

  • 在强制释放时,线程等待确认(ack)。
  • Ack确保其他线程成功获得GIL并且现在正在运行。
  • 这消除了"GIL的争夺"。

这个改进有用吗?

  • 是的,非常明显(4-core MacPro, OS-X 10.6.2)

    线程数 时间
    1 11.53s
    2 11.93s
    3 12.32s
  • 请记住,python还是被GIL在各个方面限制住(线程仍然没有提供性能上的提升
    )。

Die GIL Die!!!

然而这并没有什么卵用

  • 新的GIL会影响I/O性能。
  • 这是网络代码的一个片段:
# Thread 1
def spin():while True:# some workpass# Thread 2
def echo_server(s):while True:data = s.recv(8192)if not data:breaks.sendall(data)
  • 一个cpu bound线程在工作。
  • 一个在socket上接受和输出数据。

响应时间

  • 新的GIL增加了响应的时长。
  • 要处理I/O事件,一个线程必须经过整个超时序列才能获得控制权。
  • 忽略I/O或事件的高优先级。

不公平的唤醒/饥饿

  • 最需要的线程可能反而得不到GIL。
  • 这是由于内部条件变量的队列引起。
  • 进一步增加响应的时长。

convoy effect(FCFS算法)

  • 由于所有其他进程都等待一个大进程释放CPU,这称之为护航效果(convoy effect),与让较短进程最先执行相比,这样会导致CPU和设备使用率变的很低。
  • 不阻塞的I/O操作会导致停顿。
  • 由于I/O操作总是释放GIL,因此CPU bound的线程将一直尝试重新启动。

一个测试

  • 发送10m的数据到cpu bound的echo server线程:

    版本/cpu核心 时长
    Python 2.6.4 /2 CPU) 0.57s (10 sample average)
    Python 3.2 /2 CPU) 12.4s (20x slower)
  • 如果echo与2个CPU线程竞争怎么办?

    版本/cpu核心 时长
    Python 2.6.4 /2 CPU) 0.25s (Better performance?)
    Python 3.2 /2 CPU) 46.9s (4x slower than before)
    Python 3.2 /1 CPU) 0.14s (330x faster than 2 cores?)

理解python的GIL相关推荐

  1. 深入理解Python中的全局解释锁GIL

    深入理解Python中的全局解释锁GIL 转自:https://zhuanlan.zhihu.com/p/75780308 注:本文为蜗牛学院资深讲师卿淳俊老师原创,首发自公众号https://mp. ...

  2. 深入理解Python中的GIL(全局解释器锁)。

    Python是门古老的语言,要想了解这门语言的多线程和多进程以及协程,以及明白什么时候应该用多线程,什么时候应该使用多进程或协程,我们不得不谈到的一个东西是Python中的GIL(全局解释器锁).这篇 ...

  3. Python的GIL是什么鬼,多线程性能究竟如何

    2019独角兽企业重金招聘Python工程师标准>>> #Python的GIL是什么鬼,多线程性能究竟如何 前言:博主在刚接触Python的时候时常听到GIL这个词,并且发现这个词经 ...

  4. c++ 协程_理解Python协程(Coroutine)

    由于GIL的存在,导致Python多线程性能甚至比单线程更糟. GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种 ...

  5. python3 协程 写法_理解Python的协程(Coroutine)

    由于GIL的存在,导致Python多线程性能甚至比单线程更糟. GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种 ...

  6. 深入理解Python异步编程

    声明:本文为转载内容 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知道如何使用 Tornado.Twisted. ...

  7. 深入理解 Python 异步编程

    原文地址:点击打开链接 来源:阿驹(微信公号:驹说码事) 如有好文章投稿,请点击 → 这里了解详情 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它. ...

  8. 理解Python的协程(Coroutine)

    生成器(Generator) yield表达式的使用 生产者和消费者模型 yield from表达式 协程(Coroutine) @asyncio.coroutine async/await 总结 参 ...

  9. 完全理解 Python 迭代对象、迭代器、生成器(转)

    完全理解 Python 迭代对象.迭代器.生成器 本文源自RQ作者的一篇博文,原文是Iterables vs. Iterators vs. Generators » nvie.com,俺写的这篇文章是 ...

最新文章

  1. 是程序员,就用python导出pdf
  2. linux 系统的内核,[科普] Linux 的内核与 Linux 系统之间的关系
  3. Debug Docker: Error response from daemon: dial unix docker.raw.sock: connect: connection refused
  4. mate40pro什么时候用鸿蒙,mate40Pro什么时候可以用鸿蒙
  5. javascript --- ES6模块与CommonJS模块的差异
  6. php unserialize 实例,PHP ArrayIterator unserialize()用法及代码示例
  7. 新来个技术总监:谁在用isXxx形式定义布尔类型年后不用来了
  8. 信息学奥赛一本通C++语言——1093:计算多项式的值
  9. python怎么训练模型_GPU如何训练大批量模型?方法在这里
  10. python http通信接口开发
  11. VRRP 网关冗余备份
  12. 解决Laravel5.5版本框架缺少vender目录报错问题
  13. 简述Android操作系统和IOS系统的区别;
  14. 用户活跃/用户价值度分析
  15. matlab打印函数disp如何不换行
  16. 九天揽月带你玩转EKF纸老虎(3)
  17. Less or Equal
  18. Sample Codes之Query features from a FeatureLayer
  19. 数据安全 | 黑产研究之拖库
  20. X射线与原子核物理概论

热门文章

  1. 新手如何用QT做一个“动漫宠物“?进来学!!!!
  2. 怎样查询自己的苹果手机各个软件的大小,占用多少内存?
  3. 医学数据集及机器学习项目
  4. 翻翻棋(找规律问题)(FZU Problem 2230)
  5. MongoDB创建用户用户权限
  6. php 自然排序法,php – UCA自然排序
  7. php相亲的笑话,相亲时的笑话
  8. 程序员职场黑话一般人都不告诉他
  9. 在html中常用于定义求婚,关于求婚的英文句子中英双译
  10. 国内计算机专业学校排名及分数线,计算机专业考研院校排名及历年分数线