群里有不少的朋友来回的问我一个问题。 python线程不是有gil么? 为毛还说计数不是线程安全的么?

那么什么是原子操作?硬件的原子说的是 cpu指令集, 软件的原子可以理解为并发控制,不可被中断,加锁解决。 这也可以理解为线程安全问题。  叫法不同而已,无所谓的。。。 更多人会叫线程安全。

该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新。 http://xiaorui.cc/?p=4637

很多人其实不知道原因,大多是听人说的罢了。 首先我们可以肯定在python多线程下计数不是安全的。

我们可以用dis来论证下,python内置的dis是一个很棒的查看opcode字节码指令的模块。


Python字节码输出:

LOAD_GLOBAL加载全局变量a,LOAD_CONST加载右面的常量数据, INPLACE_ADD加法指令,a + 1, 然后用STORE_GLOBAL赋值回去。 

一个计数用了三个字节码指令, 一个是读取变量,一个是加法运算,最后一个是赋值过去。。。  线程A取到变量,然后加法运算,但是被内核的进程调度打断,执行单元被抢占….  线程B也去执行同样的方法,但是他顺利执行完毕了,这时候线程A要继续他的变量赋值。。。 但明显该数据发生变化了…

另外我们知道Python是没有volatile关键字的,原因后面有说,但是在java等语言里是有线程在cpu缓存这么一说。多个线程很大几率是跑在两个cpu core上,那么每个线程都会把获取到的变量缓存起来,那么随着时间一长,线程之间的数据的变化差距就有了。 什么时候会发生同步? 根据jvm策略或发生中断时,同步主存数据。 Python取消了volatile这样的内存屏障方法,因为他有GIL全局锁… 每次只有一个线程同时在跑,另一个线程run起来的时候,必然会触发cs,保证每次堆里面的数据不是缓存… 


另外cpu是有三级缓存的,最靠近cpu的是L1,远点的是L3, 最慢的是 主存,也就是内存。。。 线程上下文信息缓存位是从L1 –> L2 –> L3 –> main memory !!!



在python下计数不是线程安全的。那么容器类的数据结构是安全的么? 

是,安全的 !!!  

输出:

Python官方文档中和Cpython源代码中都是有说明的。


还有一个问题,python yield是线程安全的么? 我们首先可以明确生成器是迭代器的一种,多个线程针对一个生成器进行请求会发生什么? 


比如一个 list ,用yield来构建生成器来返回数据 . 不同的可迭代对象有自己的实现方法。比如 一个 列表,他的迭代器是通过下标来获取数据的。 从左到右。 怎么走? 那还是计数!!!

真的是这样么?  不….  Cpython比我们想的都要粗暴….  

输出:

单纯的看指令会发现JUMP一直循环的调用FOR_ITER,  FOR_ITER内部在不停的触发next()方法,每个可迭代对象有自己的next实现。 真正操作取值的指令是FOR_ITER,GIL保证单个opcode是安全的。


那么我们继续深入Cpython的ceval.c,发现对yield操作是不会释放gil锁,就算被内核进程调度走了,其他线程还是拿不到gil锁,trylock之后,拿到一个 generator already executing error信息。 

下面是 strace 到的python多线程并发调用生成器对象的信息….  你看到了什么?  这么线程在抢锁!!! gil作为解释器锁保证只有一个线程在跑,且粒度在于单个opcode字节码。

正常项目中不可能会出现多线程调用同一个生成器对象…  

END.



对Python及运维开发感兴趣的朋友可以加QQ群 : 478476595 !!!
{ 2000人qq大群内有各厂大牛,常组织线上分享及沙龙,对高性能及分布式场景感兴趣同学欢迎加入该QQ群 }

另外如果大家觉得文章对你有些作用!   帮忙点击广告. 一来能刺激我写博客的欲望,二来好维护云主机的费用.
如果想赏钱,可以用微信扫描下面的二维码. 另外再次标注博客原地址  xiaorui.cc  ……   感谢!
暂无相关产品

评论已关闭。