进击のpython

*****

并发编程——GIL全局解释锁


这小节就是有些“大神”批判python语言不完美之处的开始

这一节我们要了解一下Cpython的GIL解释器锁的工作机制

掌握一下GIL和互斥锁

最后再了解一下Cpython下多线程和多进程各自的应用场景

首先需要明确的一点就是GIL不是Python的特性

他是实现Python解释器(Cpython)时所引入的一个概念

当然Python不止这一个解释器来编译代码

只是因为Cpython是大部分默认环境下的Python执行环境

所以在很多人的概念里CPython就是Python

也就想当然的把GIL归结为Python语言的缺陷

所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL


GIL介绍

其实GIL本质上就是一把互斥锁,既然是互斥锁,那么所有的互斥锁本质都一样的

都是将并发编程变成串行,以此来控制同一时间内共享数据只能被一个任务所修改

进而来保证数据的安全,可以肯定的一点是:保护不同数据的安全,就应该加不同的锁

要想了解GIL,首先可以肯定一点的就是:每次执行一个py文件,都会产生一个独立的进程

比如运行1.py 2.py 3.py 就会开三个进程,而且是开三个不同的进程

在一个python的进程中,不仅是有主线程,还应该有开启的其他线程,比如垃圾回收机制级别的线程

但是这些线程都是在这个进程当中运行的,这个无需多言

而前面我们也提到了,线程之间的数据是共享的,既然数据是共享的

代码,其实本身也是数据,也是被所有线程共享的,这其中也包括解释器的代码

而程序在执行之前,需要先执行编译器的代码(这很好理解,否则你的代码仅仅是字符串)

那执行编译器的代码是不是也需要保证编译器的代码安全

所以为了保证代码的安全,我们在编译器上加了一把锁,这把锁就是GIL全局解释锁

而加了这把锁就意味着什么?就意味着python解释器同一时间只能执行一个任务代码

这样就不会出现垃圾回收代码和用户代码同时操作一个变量导致逻辑混乱的问题


GIL与Lock

那既然都已经有一把锁,来保证多线程只能一个一个运行的状态

那为什么还要有Lock这个方法呢?有过这个疑问吗?

还是那句话,加锁的目的是为了保护共享的数据,保证同一时间只能有一个线程来修改共享的数据

进而我们就应该得出结论:保护不同的数据就应该加不同的锁

那问题就变得很清晰了,GIL和Lock是两把锁,保护的数据是不一样的

前者保护的是解释器级别的代码,比如垃圾回收机制啊什么的

但是后面的则是保护的自己开发的应用程序的数据,GIL是不负责的

只能用户自己定义然后加锁处理

如果有100个线程来抢GIL锁

一定有一个线程A先抢到了GIL,然后就开始执行了,只要执行就会拿到lock.acquire()

很可能在A还没运行完,另一个线程B抢到了GIL锁,然后开始运行,看到lock没有被释放,于是就进行阻塞

阻塞的同时就会被迫交出GIL,直到A重新抢到GIL,从上次暂停的位置继续执行,直到正常释放互斥锁lock

举个例子吧:

from threading import Thread, Lock
import os, time def work():
global n
lock.acquire()
temp = n
time.sleep(0.1)
n = temp - 1
lock.release() if __name__ == '__main__':
lock = Lock()
n = 100
l = []
for i in range(100):
t = Thread(target=work)
l.append(t)
t.start()
for t in l:
t.join()
print(n)

打印的结果一定是 0 因为共享数据被保护了,只能一个一个执行


GIL与多线程

问题又出现了,进程呢,是可以利用多核,但是时间长,开销大

python的多线程开销是小,但是由于GIL的原因,不能利用多核优势

这就是在小节刚开始提的批判的‘不完美’之处

在解决这个问题之前,应该对一些问题达成共识!

CPU到底是干啥的呢?是用来计算的还是用来处理I/O阻塞的呢?

很明显是处理计算的,多个CPU是用来处理多个计算任务,换句话说,多个CPU是提高计算速度

但是当CPU遇到I/O阻塞的时候,还是需要等待的,所以,多CPU对处理阻塞没什么用

如果你的工厂是处理石材的,那工人越多效率越快(MC玩多了)

但是,如果你是等待石材过来再加工的,那等待的过程,有多少工人也没用

工人就是CPU,第一个例子就是计算密集型!第二个例子是I/O密集型!

从上面就可以看出来

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

但是,没有纯计算和纯I/O的程序,所以我们只能相对的去看一个程序到底是什么类型

所以解决问题是这样的:

方案一:开启多进程

方案二:开启多线程

单核

如果是计算密集型,没有多核的来并行计算,方案一增加了创建进程的开销

如果是I/O密集型,方案一创建的进程开销大,所以还是要选择方案二

多核

如果是计算密集型,在python中同一时刻只能一个线程执行,用不到多核,所以应该选择方案一

如果是I/O密集型,核就没用了,所以应该用方案二

但是很明显现在的计算机都是多核,python对于计算密集型的任务开多线程并不能提高效率

甚至有时候都比不上串行,但是要是对于I/O密集型任务,还是有显著提升的


性能测试

上面说的这么热闹,下面就来亲自试验一下

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

from multiprocessing import Process
from threading import Thread
import os,time
def work():
res=0
for i in range(100000000):
res*=i
if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本机为4核
start=time.time()
for i in range(4):
p=Process(target=work) #耗时5s多
p=Thread(target=work) #耗时18s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))

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

from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
time.sleep(2)
print('===>')
if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本机为4核
start=time.time()
for i in range(400):
# p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上
p=Thread(target=work) #耗时2s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))
  1. 多线程用于IO密集型,如socket,爬虫,web
  2. 多进程用于计算密集型,如金融分析

*****
*****

~~并发编程(十一):GIL全局解释锁~~的更多相关文章

  1. 10 并发编程-(线程)-GIL全局解释器锁&死锁与递归锁

    一.GIL全局解释器锁 1.引子 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势 首先需要明确的一点是GIL并不是Python的特性,它是在实现Pyt ...

  2. python 并发编程 多线程 GIL全局解释器锁基本概念

    首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念. 就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码. ...

  3. GIL全局解释锁,死锁,信号量,event事件,线程queue,TCP服务端实现并发

    一.GIL全局解释锁 在Cpython解释器才有GIL的概念,不是python的特点 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势. 1.GIL介绍 ...

  4. 20191031:GIL全局解释锁

    20191031:GIL全局解释锁 总结关于GIL全局解释锁的个人理解 GIl全局解释锁,本身不是Python语言的特性,而是Python语言底层的c Python解释器的一个特性.在其他解释器中是没 ...

  5. python网络编程--线程GIL(全局解释器锁)

    一:什么是GIL 在CPython,全局解释器锁,或GIL,是一个互斥体防止多个本地线程执行同时修改同一个代码.这把锁是必要的主要是因为当前的内存管理不是线程安全的.(然而,由于GIL存在,其他特性已 ...

  6. GIL全局解释锁

    目录 一 介绍 二 GIL介绍 三 GIL与多线程 四 多线程性能测试 一 介绍 ''' 定义: In CPython, the global interpreter lock, or GIL, is ...

  7. python中的GIL(全局解释锁)多线程能够提升效率

    预启动的时候,应用程序仍然会调用 OnLaunched 方法的,在 OnLaunched 方法调用之后,会马上发生 Suspending 事件,随后应用就会暂停. 我先基于develop主分支拉出一个 ...

  8. python GIL全局解释器锁与互斥锁 目录

    python 并发编程 多线程 GIL全局解释器锁基本概念 python 并发编程 多线程 GIL与Lock python 并发编程 多线程 GIL与多线程

  9. 并发、并行、同步、异步、全局解释锁GIL、同步锁Lock、死锁、递归锁、同步对象/条件、信号量、队列、生产者消费者、多进程模块、进程的调用、Process类、

    并发:是指系统具有处理多个任务/动作的能力. 并行:是指系统具有同时处理多个任务/动作的能力. 并行是并发的子集. 同步:当进程执行到一个IO(等待外部数据)的时候. 异步:当进程执行到一个IO不等到 ...

随机推荐

  1. 小白—职场之Java基础篇

    java基础篇 java基础 目录 1.java是一种什么语言,jdk,jre,jvm三者的区别 2.java 1.5之后的三大版本 3.java跨平台及其原理 4.java 语言的特点 5.什么是字 ...

  2. vue 生命周期钩子 路由钩子 动画钩子 执行顺序

    进入首页的钩子们 1 路由钩子 路由跳转前beforeEach 2 路由钩子 home组件内部:守卫执行前beforeRouteEnter 3.路由钩子 路由跳转后afterEach 4 生命周期 h ...

  3. Chrome插件Postman的数据目录存储位置,记一次重装系统后找回postman数据的过程...

    有次重装系统到一块新的SSD磁盘,很多数据都做了备份就是忘记将Chrome插件Postman的数据做备份,导致重装后找不到以前定义的那些Collections.悔恨之余想到既然我原来的C盘还在,为何不 ...

  4. BZOJ3242 快餐店

    原题传送门 题意 给定一个n条边n个点的连通图,求该图的某一点在该图距离最远的点距离它的距离的最小值. 题解 显然,答案是\(\frac {原图直径}{2}\). 本体的图有 \(n\) 个点 \(n ...

  5. 主流App自动化测试框架对比

        1.主流App自动化测试框架对比 2.Appium自动化测试框架 官方网址:http://appium.io/ 跨架构:支持原生.混合以及web移动应用 跨平台:Android & I ...

  6. 你从来没了解过的CSS浮动

    浮动到底是做什么呢?他们是如何影响相关元素的盒模型的呢?浮动的元素与内联元素有什么不同呢?制定浮动元素的位置的具体规则是什么?clear属性是如何工作的,并且它的作用是什么? 即使是经验丰富的开发者也 ...

  7. 常用API - Scanner、Random、ArrayList

    API 概述 API(Application Programming Interface),应用程序编程接口. Java API是一本程序员的 字典 ,是JDK中提供给我们使用的类的说明文档. 这些类 ...

  8. Git超详细用法,通俗易懂

    创建本地仓库 和 远端共享仓库 直接下载安装包:Git下载地址 安装 git,查看 git 版本,git version 配置项目的 git 账号 git config --global user.n ...

  9. RESTful API 规范(一)

    一,简介 DRF 即Django rest framework 二,rest 规范 1 协议 API 与用户通信,总是使用https协议 2 域名 1) 应尽量将API 部署在域名下(这种情况会存在跨 ...

  10. 洛谷P2566 [SCOI2009]围豆豆(状压dp+spfa)

    题目传送门 题解 Σ(っ °Д °;)っ 前置知识 射线法:从一点向右(其实哪边都行)水平引一条射线,若射线与路径的交点为偶数,则点不被包含,若为奇数,则被包含.(但注意存在射线与路径重合的情况) 这 ...