进击の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. 用Creator实现一个擀面的效果

    先上几张效果图 怎么实现的呢? 节点介绍 1是背景图,可以忽略:2 是准备好的面团:3 是擀好的面饼先隐藏:4 是需要绘制的节点:5 是擀面杖. 制作开始 首先在view上挂一个mask,并且设置为模 ...

  2. python加载json文件

    主要是加载进来,之后就没难度了 import json path = 'predict2.json' file = open(path, "rb") fileJson = json ...

  3. dart快速入门教程 (4)

    4.流程控制 4.1.分支结构 1.if语句 void main() { int score = 80; if (score >= 90) { print('优秀'); } else if (s ...

  4. Java String的相关性质分析

    引言 String可以说是在Java开发中必不可缺的一种类,String容易忽略的细节也很多,对String的了解程度也反映了一个Java程序员的基本功.下面就由一个面试题来引出对String的剖析. ...

  5. Oracle Online Patching报错"This is not a RAC setup. OPatch cannot determine the local node name"

    Oracle Online Patching报错"This is not a RAC setup. OPatch cannot determine the local node name&q ...

  6. 面试之Hashtable和ConcurrentHashMap

    那么要如何保证HashMap的线程安全呢? 方法有很多,比如使用Hashtable或者Collections.synchronizedMap,但是这两位选手都有一个共同的问题:性能.因为不管是读还是写 ...

  7. spring和springmvc包扫描问题

    写这篇博客之前,橘子松必须感慨下!!找了我一下午加一晚上(md),问了几个朋友也没找到.凉了啊 在搭建ssm之前,我把controller service mapper包扫描用基本包扫描   都写在a ...

  8. 常见的H5移动端Web页面Bug问题解决方案总汇

    解决jquery ajax调用远程接口的跨域问题 首先,接口必须允许远程调用.这是后端或者运维的事情.你必须保证你得到的一个接口是允许远程调用的.否则,就没啥了. $.ajax({ type:'get ...

  9. Python3笔记022 - 5.1 字符串常用操作

    第5章 字符串及正则表达式 5.1 字符串常用操作 5.1.1 拼接字符串 使用+运算符可完成多个字符串的拼接,产生一个新的字符串对象. str1 = "2020年07月06日是" ...

  10. MySQL 你可能忽视的选择问题

    我们在 MySQL 入门篇主要介绍了基本的 SQL 命令.数据类型和函数,在局部以上知识后,你就可以进行 MySQL 的开发工作了,但是如果要成为一个合格的开发人员,你还要具备一些更高级的技能,下面我 ...