python3 gil锁_python锁有哪几种

python3 gil锁_python锁有哪几种前言python的使用者都知道Cpython解释器有一个弊端,真正执行时同一时间只会有一个线程执行,这是由于设计者当初设计的一个缺陷,里面有个叫GIL锁的,但他到底是什么?我们只知道因为他导致pyt

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

前言

python的使用者都知道Cpython解释器有一个弊端,真正执行时同一时间只会有一个线程执行,这是由于设计者当初设计的一个缺陷,里面有个叫GIL锁的,但他到底是什么?我们只知道因为他导致python使用多线程执行时,其实一直是单线程,但是原理却不知道,那么接下来我们就认识一下GIL锁
 

什么是GIL锁

GIL(Global Interpreter Lock)不是Python独有的特性,它只是在实现CPython(Python解释器)时,引入的一个概念。在官方网站中定义如下:

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

由定义可知,GIL是一个互斥锁(mutex)。它阻止了多个线程同时执行Python字节码,毫无疑问,这降低了执行效率。理解GIL的必要性,需要了解CPython对于线程安全的内存管理机制。
 

CPython对线程安全的内存管理机制

Python使用引用计数来进行内存管理,在Python中创建的对象都会有引用计数,来记录有多少个指针指向它。当引用计数的值为0时,就会自动释放内存
 
我们来看一个小例子,来解释引用计数的原理

>>> import sys
>>> a = []
>>> b = a
>>> sys.getrefcount(a)
3

可以看到,a 的引用计数值为 3,因为有 a、b 和作为参数传递的 getrefcount 都引用了一个空列表。
如果有2个python线程同时引用a,那么2个线程都会尝试对其进行数据操作,多个线程同时对一个数据进行增加或减少的操作,如果发生这种情况,则可能导致内存泄漏
 

GIL锁的产生

由于多个线程同时对数据进行操作,会引发数据不一致,导致内存泄漏,我们可以对其进行加锁,所以Cpython就创建了GIL锁
但是既然有了锁,一个对象就需要一把锁,那么多个对象就会有多把锁,可能会给我们带来2个问题

  • 1.死锁(线程之间互相争抢锁的资源)
  • 2.反复获取和释放锁而导致性能降低。

为了保证单线程情况下python的正常执行和效率,GIL锁(单一锁)由此产生了,它添加了一个规则,即任何Python字节码的执行都需要获取解释器锁。这样可以防止死锁(因为只有一个锁),并且不会带来太多的性能开销。但这实际上使所有受CPU约束的Python程序(指的是CPU密集型程序)都是单线程的。
 

GIL锁的底层原理

python3 gil锁_python锁有哪几种
上面这张图,就是 GIL 在 Python 程序的工作示例。其中,Thread 1、2、3 轮流执行,每一个线程在开始执行时,都会锁住 GIL,以阻止别的线程执行;同样的,每一个线程执行完一段后,会释放 GIL,以允许别的线程开始利用资源。
 
线程释放GIL锁有两种情况,一是遇到IO操作,二是Time Tick到期。IO操作很好理解,比如发出一个http请求,等待响应。那么Time Tick到期是什么呢?Time Tick规定了线程的最长执行时间,超过时间后自动释放GIL锁。Python 3 以后,间隔时间大致为15毫秒
 
虽然都是释放GIL锁,但这两种情况是不一样的。比如,Thread1遇到IO操作释放GIL,由Thread2和Thread3来竞争这个GIL锁,Thread1不再参与这次竞争。如果是Thread1因为Time Tick到期释放GIL(多数是CPU密集型任务),那么三个线程可以同时竞争这把GIL锁,可能出现Thread1在竞争中胜出,再次执行的情况。单核CPU下,这种情况不算特别糟糕。因为只有1个CPU,所以CPU的利用率是很高的。
 
在多核CPU下,由于GIL锁的全局特性,无法发挥多核的特性,GIL锁会使得多线程任务的效率大大降低。
python3 gil锁_python锁有哪几种
Thread1在CPU1上运行,Thread2在CPU2上运行。GIL是全局的,CPU2上的Thread2需要等待CPU1上的Thread1让出GIL锁,才有可能执行。如果在多次竞争中,Thread1都胜出,Thread2没有得到GIL锁,意味着CPU2一直是闲置的,无法发挥多核的优势。
 
为了避免同一线程霸占CPU,在python3.2版本之后,线程会自动的调整自己的优先级,使得多线程任务执行效率更高。
既然GIL降低了多核的效率,那保留它的目的是什么呢?这就和线程执行的安全有关。
 

Python GIL不能绝对保证线程安全

def add():
    global n
    for i in range(10**1000):
        n = n +1
def sub():
    global n
    for i in range(10**1000):
        n = n - 1
n = 0
import threading
a = threading.Thread(target=add,)
b = threading.Thread(target=sub,)
a.start()
b.start()
a.join()
b.join()
print n

上面的程序对n做了同样数量的加法和减法,那么n理论上是0。但运行程序,打印n,发现它不是0。问题出在哪里呢,问题在于python的每行代码不是原子化的操作。比如n = n+1这步,不是一次性执行的。如果去查看python编译后的字节码执行过程,可以看到如下结果。

19 LOAD_GLOBAL              1 (n)
22 LOAD_CONST               3 (1)
25 BINARY_ADD          
26 STORE_GLOBAL             1 (n)

从过程可以看出,n = n +1 操作分成了四步完成。因此,n = n+1不是一个原子化操作。

  • 1.加载全局变量n
  • 2.加载常数1
  • 3.进行二进制加法运算
  • 4.将运算结果存入变量n。

根据前面的线程释放GIL锁原则,线程a执行这四步的过程中,有可能会让出GIL。如果这样,n=n+1的运算过程就被打乱了。最后的结果中,得到一个非零的n也就不足为奇。
 

总结

对于IO密集型应用,多线程的应用和多进程应用区别不大。即便有GIL存在,由于IO操作会导致GIL释放,其他线程能够获得执行权限。由于多线程的通讯成本低于多进程,因此偏向使用多线程。
 
对于计算密集型应用,由于CPU一直处于被占用状态,GIL锁直到规定时间才会释放,然后才会切换状态,导致多线程处于绝对的劣势,此时可以采用多进程+协程。

参考资料
https://realpython.com/python-gil/
https://zhuanlan.zhihu.com/p/97218985

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/165828.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • dirsearch讲解_中国指南网

    dirsearch讲解_中国指南网获取更多学习资料、想加入社群、深入学习,请扫我的二维码或加Memory20000427。在本文中,我们将学习如何使用Dirsearch。说起国内的御剑套装,相信各位都已经非常熟悉了。但御剑已经好多年不更新了,而且需要windows,有时候线程设置大了点,扫了半天中途还可能卡住不动了,也没有实时的数据保存,前功尽弃。总之各种不爽。现在好用的webscanner也层出不穷,我根据自己的需求和实际使用体验,最终选择了dirsearch这款工具。基本情况它是一个命令行工具,旨在暴力激活成功教程网站中的目录和

  • tomcat7官网下载

    tomcat7官网下载1.官网地址:tomcat.apache.org,进入后点击Tomcat72.选择不同的类型,以“64-bitWindowszip”为例3.保存文件,确定4.解压后,进入bin目录,双击startup.bat,出现下图5.打开浏览器,输入127.0.0.1:8080,出现下图,安装成功转载于:https://my.oschina.net/u/4052883/blog/29915522…

  • C++动态库和静态库_动态库和静态库调用方法

    C++动态库和静态库_动态库和静态库调用方法1、前言从事软件工作也有两年了,C++静态库和动态库也用了不少,但都是依葫芦画瓢,一直没具体去研究一下二者的区别,加载方式等,今天花时间看了几篇博客,重新学习了一下,做出如下笔记。2、定义静态库和动态库从字面意思来看,区别就是静态和动态。而这里的静态和动态指的是库的链接阶段。可以看如下的编译过程。静态库:在链接阶段库将会与目标汇编后的目标文件.o一起打包生成可执行文件。成为可执行…

  • 变量放在堆还是栈_堆和栈的共同特点

    变量放在堆还是栈_堆和栈的共同特点内容会持续更新,有错误的地方欢迎指正,谢谢!什么是栈区,什么是堆区栈区(stack):由编译器自动分配释放,存放函数的参数值、局部变量的值等,内存的分配是连续的,类似于数组,当我们声明变量时,编译器会自动接着当前栈区的结尾来分配内存。堆区(heap):由程序员分配释放,若程序员不释放,程序结束时由OS回收,内存的分配不是连续的,类似于链表。变量存放的位置C/C++程…

  • .NET NPOI导出Excel详解[通俗易懂]

    .NET NPOI导出Excel详解[通俗易懂]NPOI,顾名思义,就是POI的.NET版本。那POI又是什么呢?POI是一套用Java写成的库,能够帮助开发者在没有安装微软Office的情况下读写Office的文件。支持的文件格式包括xls,doc,ppt等。官方网站:http://npoi.codeplex.com/nuget直接获取使用一、NPOI生成Excel//创建…

  • java中数组的输出方法_java将一个数组逆序输出

    java中数组的输出方法_java将一个数组逆序输出%d:以十进制的形式输出带符号的整数。%o:以八进制的形式输出无符号的整数。%x:以十六进制的形式输出无符号整数。%u:yishijinzhid

    2022年10月11日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号