Python并发编程:多线程-GIL全局解释器锁

一 引子

在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势首先:需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比c++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码,
例如:GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是
大部分环境下默认的Python执行环境。所以很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语音的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可
以不依赖与GIL

二 GIL介绍
  GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
可以肯定一点是:保护不同的数据的安全,就应该加不同的锁
要想了解GIL,首先确定一段,每次执行Python程序,就会产生一个独立的进程。例如Python test.py,python aaa.py,python bb.py 会产生3个不同的Python进程

验证Python test.py只会产生一个进程

1

2

3

4

import os

import time

print(os.getpid())

time.sleep(30)

 

1

2

3

4

5

6

7

8

#打开终端执行

python3 test.py

#在windows下查看

tasklist |findstr python

#在linux下下查看

ps aux |grep python

  在一个Python的进程内,不仅有test.py的主线程或者由该主线程开启的其它线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有线程都运行在一个进程内

1

2

3

4

1、所有线程都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有代码以及CPython解释器的所有代码)

例如:test.py定义一个函数work(代码内容如下图),在进程内所有线程都能访问work的代码,于是我们可以开启三个线程然后target都指向该代码,能访问到意味着就是可以执行。

2、所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要项运行自己的任务,首先需要解决的是能够访问到解释器的代码  

  综上:

如果多个线程的target=work,那么执行流程是:

多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行

  解释器的代码是所有线程共享,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有声明高明的方法,就是加锁处理,如下图的GIL,保证Python解释器同一时间只能执行一个任务的代码

三 GIL与Lock 
  问题:Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock?
首先:我们需要达成共识:锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据

然后:我们可以得出结论:保护不同的数据就应该加不同的锁

最后:问题是很明朗了,GIL与LOCK是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL
负责这件事,只能用户自定义加锁处理,即Lock,如下图:

分析:

1

2

3

4

5

6

7

1100个线程去抢GIL锁,即抢执行权限

2、肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()

3、极有可能线程1还未执行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL

4、直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其它的线程再重复2,3,4的过程  

示例代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

from threading import Thread,Lock

import os

import time

def work():

    global n

    lock.acquire()

    temp = n

    time.sleep(0.5)

    = temp - 1

    lock.release()

if __name__ == '__main__':

    lock = Lock()

    = 100

    = []

    for in range(100):

        = Thread(target=work)

        l.append(p)

        p.start()

    for in l:

        p.join()

    print(n)    # 结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全,不加锁则结果可能为99

 

  四 GIL与多线程

有了GIL的存在,同一时刻同一进程中只有一个线程被执行

问题:进程可以利用多核,但是开销大,而Python的多线程开销小,但却无法利用多核优势,也就是说Python没用了,PHP才是最牛B的语言?

要解决这个问题,我们需要在几个点上达成一致:

1

2

3

4

5

1、CPU到底是用来做计算的,还是用来做I/O的?

2、多CPU,意味着可以有多个核并行完成计算,所以多核提升的是计算性能

3、每个CPU一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用处

  一个工人相当于CPU,此时计算相当于工人在干活,I/O阻塞相当于为工人干活提供所需原材料的过程,工人干活的过程中如果没有原材料了,则工人干活的过程需要停止,直到原材料的到来

如果你的工厂的大多数任务需要有准备原材料的过程(I/O密集型),那么你有再多的工人,意义也不大,还不如一个人,在等材料的过程中让工人去干别的活

反过来讲,如果你的工厂原材料都齐全,那当然是工人越多,效率越高

结论:

1

2

3

1、对计算来说,CPU越多越好,但是对于I/O来说,再多的CPU也没用

2、当然对运行一个程序来说,随着CPU的增多执行效率肯定会有所提高(不管提高幅度多大,总会有所提高),这是因为一个程序基本上不会是纯计算或者纯I/O,所以我们只能相对的去看一个程序到底是计算密集型还是I/O密集型,从而进一步分享Python的多线程到底有无用武之地  

假设我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:

1

2

3

方案一:开启四个进程

方案二:一个进程下,开启四个线程

 

单核情况下,分析结果:

1

2

如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜

如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜

多核情况下,分析结果:

1

2

如果四个任务是计算密集型,多核意味着并行计算,在Python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜

如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜  

结论:
现在计算机基本上都是多核,Python对于计算密集型的任务开多个线程的效率并不能带来多大性能上的提升,甚者不如串行(没有大量切换),但是,对于I/O密集型的任务效率还是有显著提升的

五  多线程性能测试


如果并发的多个任务是计算 密集型:多进程效率高

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

from multiprocessing import Process

from threading import Thread

import os,time

def work():

    res = 0

    for in range(10000):

        res *= i

if __name__ == '__main__':

    = []

    print(os.cpu_count())

    start = time.time()

    for in range(4):

        = Process(target=work)

        = Thread(target=work)

        l.append(p)

        p.start()

    for in l:

        p.join()

    stop = time.time()

    print('运行时间: %s' % (stop-start))

 

  如果并发的多个任务是I/O密集型:多线程效率高

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

from threading import Thread

import threading

import os,time

def work():

    time.sleep(2)

    print('===>')

if __name__ == '__main__':

    = []

    print(os.cpu_count())

    start = time.time()

    for in range(400):

        #p = Process(target=work)

        = Thread(target=work)     # 耗时2秒多

        l.append(p)

        p.start()

    for in l:

        p.join()

    stop = time.time()

    print('运行时间:%s' %(stop-start))

  

应用

1

2

多线程用于IO密集型,如socket,爬虫,web

多进程用于计算密集型,如金融分析

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

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

相关文章

协议(网络协议)

HTTP/HTTPS 协议 HTTP 实际上是个缩写,英文全称是:Hyper Text Transfer Protocol (超文本传输协议)。 最常用的网页(也叫web页)就是一种超文本的具体表现形式。HTTPS (全称:Hyper …

美团-放水果

题目: 放水果 把M个相同的水果放在N个同样的盘子里,允许有的盘子空着不放,问不同的放法数K是多少?请注意,5,1,1和1,5,1 是同一种放法。输入描述 第一行是测试数据的数目…

【Spring】19 @Autowired注解使用详解

文章目录 构造函数注入Setter方法注入字段注入数组和集合注入特殊情况处理特殊接口类型的注入异常处理结语 Spring 框架的 Autowired 注解是实现依赖注入的一种强大而灵活的方式。在本文中,我们将介绍 Autowired 注解的多种用法,包括构造函数、setter方法…

ICASSP2024 | ICMC-ASR 车载多通道语音识别挑战赛总结

为促进驾驶场景中语音处理和识别研究,在ISCSLP 2022上成功举办智能驾驶座舱语音识别挑战 (ICSRC)的基础上,西工大音频语音与语言处理研究组 (ASLPNPU)联合理想汽车、希尔贝壳、WeNet社区、字节、微软、天津大学、南洋理工大学以及中国信息通信研究院等多…

EMO在哪体验?阿里对口型视频生成工具EMO下载地址?阿里巴巴新模型EMO的技术原理

这几天,阿里的对口型视频生成工具EMO火了。根据官方宣传,EMO只需要上传一张图片和一段音频就可以一键生成对口型视频,而且视频中的嘴型还可以与声音匹配。这项技术支持多语言、对话、唱歌以及快速语速的适配,但也可能成为制造虚假…

pip降级在pycharm中

PyCharm依赖于"–build-dir"参数安装第三方库,但该参数在最新的23.0版pip中已删除 解决办法就是降级pip,PyCharm中选择File,找到编译器,点击pip,勾选对应版本即可 或者在cmd中执行运行python -m pip install…

基于centos的linux上docker安装,及mysql、redis等应用在docker容器中的安装

Docker环境安装 安装yum-utils: yum install ‐y yum‐utils device‐mapper‐persistent‐data lvm2为yum源添加docker仓库位置: yum‐config‐manager ‐‐add‐repo https://download.docker.com/linux/centos/docker‐ce.repo如果上面执行命令后…

【matlab】matlab随机函数-rand

matlab中rand相关的随机函数包括rand(),randn(),randi()等。相关用法如下: 1,rand(m,n) 含义:生成0-1间均匀分布的随机矩阵(m行,n列),如果mn,则可简写为rand(m) >> rand(1) ans 0.8147 ----------…

Linux系统中的高级多线程编程技术

在Linux系统中,多线程编程是一种常见的并发编程模型,通过利用多线程可以实现程序的并发执行,提高系统的性能和响应速度。在Linux系统中,开发人员通常使用 pthread 库来进行多线程编程,同时需要掌握线程同步技术以避免并…

JVM(4)

垃圾回收问题 垃圾回收算法 通过之前的学习我们可以将死亡对象标记出来了,标记出来后我们就可以进行垃圾回收操作了,在正式学习垃圾处理器之前,我们先来看一下垃圾回收器使用的几种算法. 标记-清除算法 "标记-清除"算法是基础的收集算法.算法分为"标记"…

「Vue3系列」Vue3指令

文章目录 一、Vue3 指令二、注册-自定义指令三、常见自定义指令1. 聚焦指令(v-focus)2. 高亮指令(v-highlight)3. 防抖指令(v-debounce)4. 限制输入指令(v-limit)使用注意事项 四、相…

WPF中如何设置自定义控件

1.圆角按钮的设置: 众所周知在WPF中自带有提示信息,当我问创建Button时,点击空格出现如下可选设置 带有小扳手🔧图标为相应的属性,如果Button有CornerRadius(角半径)属性就能够直接设置Button实…

33. 【Linux教程】Linux 用户组

前面小节介绍了 Linux 用户相关的增删改查,本小节介绍 Linux 用户组,Linux 系统中采取了一种安全机制(即用户组),用户组可以允许多个 Linux 用户共享同一种权限。 1. 用户组介绍 Linux 是多任务多用户的操作系统&…

鸿蒙Harmony应用开发—ArkTS声明式开发(自定义事件分发)

ArkUI在处理触屏事件时,会在触屏事件触发前进行按压点和组件区域的触摸测试,来收集需要响应触屏事件的组件,再基于触摸测试结果分发相应的触屏事件。在父节点,开发者可以通过onChildTouchTest决定如何让子节点去做触摸测试&#x…

【AI Agent系列】【MetaGPT多智能体学习】5. 多智能体案例拆解 - 基于MetaGPT的智能体辩论(附完整代码)

本系列文章跟随《MetaGPT多智能体课程》(https://github.com/datawhalechina/hugging-multi-agent),深入理解并实践多智能体系统的开发。 本文为该课程的第四章(多智能体开发)的第三篇笔记。主要是对课程刚开始环境搭…

Linux系统——Shell脚本——一键安装LNMP

#!/bin/bash #安装nginx echo "安装nginx服务" wget http://nginx.org/download/nginx-1.11.4.tar.gz &>/dev/null if [ $? -eq 0 ] thenecho "nginx-1.11.4安装包下载完成"echo "--开始安装必要的依赖文件--"yum install -y gcc gcc-c…

python中map函数

map(str, path): map函数会将path中的每一个元素传递给str函数,从而将它们转换为字符串。 如果path是一个数字列表,例如[1, 2, 3],那么map(str, path)将返回[1, 2, 3]。 在写二叉树时用到map给树节点进行str转换是错的。 map(s…

xsslabs第五关

看一下源码 <!DOCTYPE html><!--STATUS OK--><html> <head> <meta http-equiv"content-type" content"text/html;charsetutf-8"> <script> window.alert function() { confirm("完成的不错&#xff01…

MATLAB知识点:条件判断 if-elseif-else-end语句

​讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 节选自​第4章&#xff1a;MATLAB程序流程控制 if、elseif、…

webstorm 创建运行纯Typescript项目

创建一个空项目&#xff0c;在项目根目录创建一个tsconfig.json文件自动配置&#xff1a; 打开终端输入tsc --init&#xff0c;即可自动生成tsconfig.json文件手动配置&#xff1a; 在项目根目录下新建一个tsconfig.json文件,并配置如下内容 具体配置可以直接使用下面的配置&am…