理解python的GIL
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相关推荐
- 深入理解Python中的全局解释锁GIL
深入理解Python中的全局解释锁GIL 转自:https://zhuanlan.zhihu.com/p/75780308 注:本文为蜗牛学院资深讲师卿淳俊老师原创,首发自公众号https://mp. ...
- 深入理解Python中的GIL(全局解释器锁)。
Python是门古老的语言,要想了解这门语言的多线程和多进程以及协程,以及明白什么时候应该用多线程,什么时候应该使用多进程或协程,我们不得不谈到的一个东西是Python中的GIL(全局解释器锁).这篇 ...
- Python的GIL是什么鬼,多线程性能究竟如何
2019独角兽企业重金招聘Python工程师标准>>> #Python的GIL是什么鬼,多线程性能究竟如何 前言:博主在刚接触Python的时候时常听到GIL这个词,并且发现这个词经 ...
- c++ 协程_理解Python协程(Coroutine)
由于GIL的存在,导致Python多线程性能甚至比单线程更糟. GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种 ...
- python3 协程 写法_理解Python的协程(Coroutine)
由于GIL的存在,导致Python多线程性能甚至比单线程更糟. GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种 ...
- 深入理解Python异步编程
声明:本文为转载内容 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知道如何使用 Tornado.Twisted. ...
- 深入理解 Python 异步编程
原文地址:点击打开链接 来源:阿驹(微信公号:驹说码事) 如有好文章投稿,请点击 → 这里了解详情 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它. ...
- 理解Python的协程(Coroutine)
生成器(Generator) yield表达式的使用 生产者和消费者模型 yield from表达式 协程(Coroutine) @asyncio.coroutine async/await 总结 参 ...
- 完全理解 Python 迭代对象、迭代器、生成器(转)
完全理解 Python 迭代对象.迭代器.生成器 本文源自RQ作者的一篇博文,原文是Iterables vs. Iterators vs. Generators » nvie.com,俺写的这篇文章是 ...
最新文章
- 是程序员,就用python导出pdf
- linux 系统的内核,[科普] Linux 的内核与 Linux 系统之间的关系
- Debug Docker: Error response from daemon: dial unix docker.raw.sock: connect: connection refused
- mate40pro什么时候用鸿蒙,mate40Pro什么时候可以用鸿蒙
- javascript --- ES6模块与CommonJS模块的差异
- php unserialize 实例,PHP ArrayIterator unserialize()用法及代码示例
- 新来个技术总监:谁在用isXxx形式定义布尔类型年后不用来了
- 信息学奥赛一本通C++语言——1093:计算多项式的值
- python怎么训练模型_GPU如何训练大批量模型?方法在这里
- python http通信接口开发
- VRRP 网关冗余备份
- 解决Laravel5.5版本框架缺少vender目录报错问题
- 简述Android操作系统和IOS系统的区别;
- 用户活跃/用户价值度分析
- matlab打印函数disp如何不换行
- 九天揽月带你玩转EKF纸老虎(3)
- Less or Equal
- Sample Codes之Query features from a FeatureLayer
- 数据安全 | 黑产研究之拖库
- X射线与原子核物理概论