解释为脑瘫的那张图_Python GIL全局解释器锁详解(深度剖析)

通过前面的学习,我们了解了 Pyton 并发编程的特性以及什么是多线程编程。其实除此之外,Python 多线程还有一个很重要的知识点,就是本节要讲的 GIL。GIL,中文译为全局解释器锁。在讲解 GIL 之前,首先通过一个例子来直观感受一下 GIL 在 Python 多线程程序运行的影响。首先运行如下程序:

import timestart = time.clock()def CountDown(n):    while n > 0:        n -= 1CountDown(100000)print("Time used:",(time.clock() - start))

运行结果为:

Time used: 0.0039529000000000005

在我们的印象中,使用多个(适量)线程是可以加快程序运行效率的,因此可以尝试将上面程序改成如下方式:

import timefrom threading import Threadstart = time.clock()def CountDown(n):    while n > 0:        n -= 1t1 = Thread(target=CountDown, args=[100000 // 2])t2 = Thread(target=CountDown, args=[100000 // 2])t1.start()t2.start()t1.join()t2.join()print("Time used:",(time.clock() - start))

运行结果为:

Time used: 0.006673

可以看到,此程序中使用了 2 个线程来执行和上面代码相同的工作,但从输出结果中可以看到,运行效率非但没有提高,反而降低了。

如果使用更多线程进行尝试,会发现其运行效率和 2 个线程效率几乎一样(本机器测试使用 4 个线程,其执行效率约为 0.005)。这里不再给出具体测试代码,有兴趣的读者可自行测试。

是不是和你猜想的结果不一样?事实上,得到这样的结果是肯定的,因为 GIL 限制了 Python 多线程的性能不会像我们预期的那样。那么,什么是 GIL 呢?GIL 是最流程的 CPython 解释器(平常称为 Python)中的一个技术术语,中文译为全局解释器锁,其本质上类似操作系统的 Mutex。GIL 的功能是:在 CPython 解释器中执行的每一个 Python 线程,都会先锁住自己,以阻止别的线程执行。当然,CPython 不可能容忍一个线程一直独占解释器,它会轮流执行 Python 线程。这样一来,用户看到的就是“伪”并行,即 Python 线程在交替执行,来模拟真正并行的线程。有读者可能会问,既然 CPython 能控制线程伪并行,为什么还需要 GIL 呢?其实,这和 CPython 的底层内存管理有关。CPython 使用引用计数来管理内容,所有 Python 脚本中创建的实例,都会配备一个引用计数,来记录有多少个指针来指向它。当实例的引用计数的值为 0 时,会自动释放其所占的内存。举个例子,看如下代码:

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

可以看到,a 的引用计数值为 3,因为有 a、b 和作为参数传递的 getrefcount 都引用了一个空列表。假设有两个 Python 线程同时引用 a,那么双方就都会尝试操作该数据,很有可能造成引用计数的条件竞争,导致引用计数只增加 1(实际应增加 2),这造成的后果是,当第一个线程结束时,会把引用计数减少 1,此时可能已经达到释放内存的条件(引用计数为 0),当第 2 个线程再次视图访问 a 时,就无法找到有效的内存了。所以,CPython 引进 GIL,可以最大程度上规避类似内存管理这样复杂的竞争风险问题。

Python GIL底层实现原理

c84ba37a50f647d3b824f132738b5fe6.png图 1 GIL 工作流程示意图

上面这张图,就是 GIL 在 Python 程序的工作示例。其中,Thread 1、2、3 轮流执行,每一个线程在开始执行时,都会锁住 GIL,以阻止别的线程执行;同样的,每一个线程执行完一段后,会释放 GIL,以允许别的线程开始利用资源。读者可能会问,为什么 Python 线程会去主动释放 GIL 呢?毕竟,如果仅仅要求 Python 线程在开始执行时锁住 GIL,且永远不去释放 GIL,那别的线程就都没有运行的机会。其实,CPython 中还有另一个机制,叫做间隔式检查(check_interval),意思是 CPython 解释器会去轮询检查线程 GIL 的锁住情况,每隔一段时间,Python 解释器就会强制当前线程去释放 GIL,这样别的线程才能有执行的机会。注意,不同版本的 Python,其间隔式检查的实现方式并不一样。早期的 Python 是 100 个刻度(大致对应了 1000 个字节码);而 Python 3 以后,间隔时间大致为 15 毫秒。当然,我们不必细究具体多久会强制释放 GIL,读者只需要明白,CPython 解释器会在一个“合理”的时间范围内释放 GIL 就可以了。整体来说,每一个 Python 线程都是类似这样循环的封装,来看下面这段代码:

for (;;) {    if (--ticker < 0) {        ticker = check_interval;           /* Give another thread a chance */        PyThread_release_lock(interpreter_lock);        /* Other threads may run now */           PyThread_acquire_lock(interpreter_lock, 1);    }    bytecode = *next_instr++;    switch (bytecode) {        /* execute the next instruction ... */    }}

从这段代码中可以看出,每个 Python 线程都会先检查 ticker 计数。只有在 ticker 大于 0 的情况下,线程才会去执行自己的代码。

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

注意,有了 GIL,并不意味着 Python 程序员就不用去考虑线程安全了,因为即便 GIL 仅允许一个 Python 线程执行,但别忘了 Python 还有 check interval 这样的抢占机制。比如,运行如下代码:

import threadingn = 0def foo():    global n    n += 1threads = []for i in range(100):    t = threading.Thread(target=foo)    threads.append(t)for t in threads:    t.start()for t in threads:    t.join()print(n)

执行此代码会发现,其大部分时候会打印 100,但有时也会打印 99 或者 98,原因在于 n+=1 这一句代码让线程并不安全。如果去翻译 foo 这个函数的字节码就会发现,它实际上是由下面四行字节码组成:

>>> import dis
>>> dis.dis(foo)
LOAD_GLOBAL              0 (n)
LOAD_CONST               1 (1)
INPLACE_ADD
STORE_GLOBAL             0 (n)

而这四行字节码中间都是有可能被打断的!所以,千万别以为有了 GIL 程序就不会产生线程问题,我们仍然需要注意线程安全。

957800d9a112fe3eb7cdc7e099f3cf43.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/427956.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SPOJ3276 D-query

题意&#xff1a;n个数 a1...an&#xff0c;q组询问&#xff0c;每组询问给定 l&#xff0c;r&#xff0c;输出 [ l, r ] 有多少不同的数 ( n ≤30000, q ≤200000, ai ≤ 106 ) 离线 树状数组维护 1 #include<bits/stdc.h>2 3 using namespace std;4 5 const int MAXN…

关于string型的处理——pta L1常用

1.关于带空格字符串函数的输入 a.getline(cin,str); b.getline(cin,str,c);(c为字符。) #include<bits/stdc.h> using namespace std; int main() {string str1,str2;getline(cin,str1); //输入带空格的字符串&#xff0c;当输入空格的时候终止 getline(cin…

电路板上的插头怎么拔下来_空调维修排查电路板内外原因

点击链接&#xff1a;空调变频板维修在线课程来了空调维修资料库重大更新&#xff0c;正在上传中&#xff0c;点击马上拥有排查电路板内外原因熟悉电路结构&#xff0c;先分清控制板的内外电路&#xff0c;外部检测、外部控制等&#xff0c;分清故障产生是内因还是外因——确定…

在比赛中一些对数和数组的操作的应用(持续更新)

1.整形数组的排序-sort函数&#xff08;在比赛中如果比赛不卡时间的话可以节省不少时间&#xff09; sort(na,nb);(n为你想要进行排序的整形数组)&#xff1b; #include<bits/stdc.h> using namespace std; int main() {int num[10]{1,23,232,213,112,123,342,42,121,12…

软件测试知识体系结构整理(一)(个人为备战期中期末理论考试所总结,如需要系统学习关于软件测试的知识,请参考其他博主)

系列文章目录 第一章 软件测试基本概念 文章目录系列文章目录一、软件测试基本概念1.软件缺陷2.软件缺陷类型3.软件缺陷等级4.关于对软件测试的一般误区5.软件测试环境二、白盒测试1.白盒测试的概念与优缺点理解2.逻辑测试3.基本路径测试三.黑盒测试1.等价类测试2.边界值分析法…

算法训练 6-1 递归求二项式系数值

算法训练 6-1 递归求二项式系数值 问题描述样例输入一个满足题目要求的输入范例。3 10样例输出与上面的样例输入对应的输出。数据规模和约定输入数据中每一个数的范围。例&#xff1a;结果在int表示时不会溢出。 import java.util.Scanner;public class Main {public static v…

印象笔记mac版 同步问题_印象笔记表示 今年将大幅提升产品体验

【手机中国新闻】尽管去年取得了一些成绩&#xff0c;但印象笔记依旧在业务泥潭中苦苦挣扎&#xff0c;该公司一度裁员15%并承受了一些高管的离职。Ian Small去年10月起取代2015年上任的Chris O Neill成为印象笔记现任CEO&#xff0c;相较于一些相关的老生常谈的创新&#xff0…

函数基本语法及特性

我先复制了下面一段&#xff0c;挺有意思的。 背景提要 现在老板让你写一个监控程序&#xff0c;监控服务器的系统状况&#xff0c;当cpu&#xff3c;memory&#xff3c;disk等指标的使用量超过阀值时即发邮件报警&#xff0c;你掏空了所有的知识量&#xff0c;写出了以下代码 …

将一个项目发布到Tomcat上并进行运行

1.下载任意版本的Tomcat&#xff0c;我这里是9的版本&#xff0c;应该是当前我认为比较好用的一个版本了&#xff0c;有需要的话可以到公众号自取。 微信公众号搜索“是短短吖” 后台回复“Tomcat”即可。 2.在webapp下部署一个项目Test&#xff08;名字自选&#xff0c;英文…

利用在Tomcat上部署servlet程序(手动布置加强关于servlet知识的理解,当前的idea是可以实现自动部署的)

一.手动部署 1.在idea里简单的建立一个测试的项目&#xff0c;创建一个简单的java文件&#xff0c;创建一个简单的类&#xff0c;进行继承HttpServlet&#xff0c;我这里以TT为例。&#xff08;PS:为避免后续麻烦&#xff0c;代码会最后给出全部&#xff0c;理解过程即可&…

Xcode9的xib只支持iOS7.0及以上版本

Xcode升级到9以后&#xff0c;对xib的最低编译版本有了限制&#xff0c;以前未指定版本的xib会报错&#xff0c;如下 遇到这个情况&#xff0c;根据错误提示很明显提示的是ib文件支持的最低版本是iOS7&#xff0c;所以我就将该xib的版本设为iOS7.0&#xff0c;如下 未指定版本的…

UI设计PS初学入门知识总结——新建的理论知识

1.一般设计的类图分类 2.新建之尺寸&#xff08;宽度&#xff0c;高度&#xff09; 分为两种&#xff0c;一种是以像素为单位&#xff0c;一种是为真实长度为单位。 一类图一般以像素为单位&#xff0c;二类图必须以真实长度为单位。 宽度和高度的细节因素根据实际需求去做&a…

20172329 2017-2018-2 《程序设计与数据结构》实验一报告

20172329 2017-2018-2 《程序设计与数据结构》实验一报告 课程&#xff1a;《程序设计与数据结构》 班级&#xff1a; 1723 姓名&#xff1a; 王文彬 学号&#xff1a;20172329 实验教师&#xff1a;王志强 实验日期&#xff1a;2018年3月21日 必修/选修&#xff1a; 必修 1.实…

VS集成Qt开发入门(简易时间显示)

VS集成Qt开发入门&#xff08;简易时间显示&#xff09;软件开发入门开发环境简单时间显示&#xff08;LcdNumber&#xff09;ui界面设计&#xff08;clock.ui&#xff09;工程文件&#xff08;clock.h&#xff0c;clock.cpp&#xff09;头文件clock.h&#xff1a;源文件clock.…

ps 毛发 边缘_不会抠图怎么办?PS画笔绘制毛发技巧,抠图流程解析

前两期我们讲了一些简单的抠像方法。第一次我们知道了如何在抠像的同时保留人物投影。第二次我们知道了如何利用背景橡皮擦工具进行抠像。以上是一些稍微简单一些的抠图方法&#xff0c;人人都可以轻松办到。今天讲如何利用PS画笔绘制毛发。今天的知识稍微需要点耐心。如何把下…

基于结构体的二进制文件读写

基于结构体的二进制文件读写项目介绍工程创建结构体的创建写二进制文件结果读二进制文件结果参考文献项目介绍 本次设计是为了提高读写二进制文件的效率&#xff0c;以约定的结构体形式进行读写操作&#xff0c;避免了一个字节一个字节的多次写入读取操作&#xff0c;并且能以…

排序算法-希尔排序

上一篇讲解了简单插入排序算法&#xff0c;以及在其基础上优化的二分插入排序算法&#xff0c;但是每次插入需要按间隔为 1 移动有序区的元素&#xff0c;效率不高&#xff0c;下面我们来介绍一种新的插入排序算法-希尔排序。 算法简介 希尔排序&#xff08;Shell Sort&#xf…

封包时发现的关于QIODevice类write函数的坑

关于QIODevice类write函数的坑问题概述问题部分代码问题解决结论问题概述 这两天在做TCP通信的封包解包协议操作时&#xff0c;不经意间被write函数坑了好久。通过内存复制进行数据封包&#xff0c;在写入socket的时候总是写入一个字节&#xff0c;不能全部写入数据&#xff0…

Qt多文件传输功能实现及方法概述

Qt多文件传输功能实现前言代码实现概述客户端代码实现服务端代码效果展示结论前言 本次设计主要是为了功能上的实现&#xff0c;因此对于ui界面的设计都是怎么简单怎么来的&#xff0c;主要的功能就是实现多个文件的发送与接收&#xff0c;即客户端发送&#xff0c;服务端接收…

从java到C++入门

C基础知识前言基础前言 当初为了赶一波互联网热潮自学了java&#xff0c;如今因为需要就从java转向C开发&#xff0c;于是就有了java到C入门&#xff0c;每次的学习我都会记录一下C的学习历程。 基础 C对于内存的控制管理比java要有更多要求&#xff0c;因此C对于变量的创建…