~~并发编程(十一):GIL全局解释锁~~
进击の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))
多线程用于IO密集型,如socket,爬虫,web多进程用于计算密集型,如金融分析
*****
*****
~~并发编程(十一):GIL全局解释锁~~的更多相关文章
- 10 并发编程-(线程)-GIL全局解释器锁&死锁与递归锁
一.GIL全局解释器锁 1.引子 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势 首先需要明确的一点是GIL并不是Python的特性,它是在实现Pyt ...
- python 并发编程 多线程 GIL全局解释器锁基本概念
首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念. 就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码. ...
- GIL全局解释锁,死锁,信号量,event事件,线程queue,TCP服务端实现并发
一.GIL全局解释锁 在Cpython解释器才有GIL的概念,不是python的特点 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势. 1.GIL介绍 ...
- 20191031:GIL全局解释锁
20191031:GIL全局解释锁 总结关于GIL全局解释锁的个人理解 GIl全局解释锁,本身不是Python语言的特性,而是Python语言底层的c Python解释器的一个特性.在其他解释器中是没 ...
- python网络编程--线程GIL(全局解释器锁)
一:什么是GIL 在CPython,全局解释器锁,或GIL,是一个互斥体防止多个本地线程执行同时修改同一个代码.这把锁是必要的主要是因为当前的内存管理不是线程安全的.(然而,由于GIL存在,其他特性已 ...
- GIL全局解释锁
目录 一 介绍 二 GIL介绍 三 GIL与多线程 四 多线程性能测试 一 介绍 ''' 定义: In CPython, the global interpreter lock, or GIL, is ...
- python中的GIL(全局解释锁)多线程能够提升效率
预启动的时候,应用程序仍然会调用 OnLaunched 方法的,在 OnLaunched 方法调用之后,会马上发生 Suspending 事件,随后应用就会暂停. 我先基于develop主分支拉出一个 ...
- python GIL全局解释器锁与互斥锁 目录
python 并发编程 多线程 GIL全局解释器锁基本概念 python 并发编程 多线程 GIL与Lock python 并发编程 多线程 GIL与多线程
- 并发、并行、同步、异步、全局解释锁GIL、同步锁Lock、死锁、递归锁、同步对象/条件、信号量、队列、生产者消费者、多进程模块、进程的调用、Process类、
并发:是指系统具有处理多个任务/动作的能力. 并行:是指系统具有同时处理多个任务/动作的能力. 并行是并发的子集. 同步:当进程执行到一个IO(等待外部数据)的时候. 异步:当进程执行到一个IO不等到 ...
随机推荐
- vue全家桶(2.3)
3.4.嵌套路由 实际生活中的应用界面,通常由多层嵌套的组件组合而成.同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如: 再来看看下面这种更直观的嵌套图: 接下来我们需要实现下面这种 ...
- vue基础入门(2.1)
2.vue基础用法 2.1.事件处理 2.1.1.监听事件 使用v-on:事件名称 = '事件处理函数'的形式来监听事件,事件处理函数要写在methods后面的对象中 <!DOCTYPE htm ...
- STL初步学习(map)
3.map map作为一个映射,有两个参数,第一个参数作为关键值,第二个参数为对应的值,关键值是唯一的 在平时使用的数组中,也有点类似于映射的方法,例如a[10]=1,但其实我们的关键值和对应的值只能 ...
- 《算法笔记》9.4小节 问题 B: 二叉搜索树
这道题也当做二叉搜索树的建树模板. 这道题其实直接把这颗树建出来后,比较前序序列和中序序列即可,这里我用的数组实现,更好写和查错qwq. code: #include <bits/stdc++. ...
- Java多线程可重入锁例子解析
“可重入锁”的概念是:自己可以再次获得自己的内部锁.比如有一条线程获得了某个对象的锁,此时这个对象还没有释放,当其再次想获得这个对象的锁的时候还是可以获得的,如果不可锁重入的话,就会造成死锁. cla ...
- Oracle收集对表收集统计信息导致全表扫描直接路径读?
direct path read深入解析 前言 最近碰到一件很奇葩的事情,因为某条SQL执行缓慢,原因是走了笛卡尔(两组大数据结果集),而且笛卡尔还是NL的一个部分,要循环31M次. 很容易发现是统计 ...
- web前端开发书籍推荐_css/css3的好书有哪些?
css/css3样式已是web前端开发的主流技术了.每个优秀的前端程序员都应该熟悉,甚至精通css.那么要如何才能学好css,并很好的应用到实际开发中,这篇文章就推荐一些关于css相关的书籍给大家. ...
- OAuth 2.0 授权方式讲解,规范实践和应用
基于实践说规范 网上看了一些OAuth 2.0的授权方法,尽管讲解的没有什么逻辑性错误,但是存在一个问题,那就是单纯的讲解协议规范却脱离了实际的应用,缺少干货,所以才有了这篇文章,内容基于实际业务进行 ...
- Tallest Cow,题解
题目链接 题意: 问满足一系列形如ab可以相互看到的约束的所有奶牛的最大身高(最高的编号和高度已给出) 分析: 首先,这个可以互相看到指的是中间的人比两头的都矮,一条斜线看到的不行,那么其实我们就可以 ...
- 树的子结构(剑指offer-17)
题目描述 输入两棵二叉树A,B,判断B是不是A的子结构.(ps:我们约定空树不是任意一个树的子结构) 解析 解答 /** public class TreeNode { int val = 0; Tr ...