并发编程之GIL
GIL
什么是GIL锁
官方解释:
'''
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
native threads from executing Python bytecodes at once. This lock is necessary mainly
because CPython’s memory management is not thread-safe. (However, since the GIL
exists, other features have grown to depend on the guarantees that it enforces.)
'''
释义:
在CPython中,这个全局解释器锁,也称为GIL,是一个互斥锁,防止多个线程在同一时间执行Python字节码,这个锁是非常重要的,因为CPython的内存管理非线程安全的,很多其他的特性依赖于GIL,所以即使它影响了程序效率也无法将其直接去除
总结:
在CPython中,GIL会把线程的并行变成串行,导致效率降低
为什么需要加锁
# 线程安全问题具体的表现
# cpython解释器与python程序之间的关系?
python程序本质就是一堆字符串,所有运行一个python程序时,必须要开启一个解释器
但是在一个python程序中解释器只有一个,所有代码都要交给他来解释执行
当有多个线程都要执行代码时,就会产生线程安全问题。
那我们不开启子线程是不是就没有问题了?
答案是:不是。
# Cpython解释器与GC问题
python会自动帮我们处理垃圾,清扫垃圾也是一对代码,也需要开启一个线程来执行
也就是说计算程序没有自己开启线程,内部也有多个线程
GC线程与我们程序中的线程就会产生安全问题
例如:
线程a要定义一个变量
步骤:先申请一块空的内存,在吧数据装进去,最后将引用计数加1
如果在进行到第二部的时候,CPU切换到Gc线程,GC就会把这个值当成垃圾,清理掉
总结:为了解决线程安全问题
带来的问题
GIL是一把互斥锁,互斥锁将导致效率低
具体表现是Cpython中,即便开启了多线程,而且CPU也是多核的,却无法并行执行任务。
因为解释器只有一个,同一时间只能有一个任务在执行
如何解决
没办法解决,只能尽可能的避免GIL锁影响我们的效率
1. 使用多进程能够实现并行,从而更好的利用多核CPU
2. 对任务进行区分
任务可以分为两类
1. 计算密集型,基本没有IO 大部分时间都在计算,例如人脸识别,图像处理
由于多线程不能并行,应该使用多进程,将任务分给不同CPU核心
2. IO密集型,计算任务非常少,大部分时间都在等待IO操作
由于网络IO速度对比CPU处理速度非常慢,多线程并不会造成太大的影响
对于网络IO密集型,因为有大量客户端连接服务,进程根本开不起来,只能多线程
关于性能的讨论
之所以加锁是为了解决线程安全问题
由于有了锁,导致了Cpython中多线程不能并行,只能并发
但是我们不能因此否认python
1. python是一门语言,GIL是Cpython解释器的问题,还有Jpython,pypy
2. 如果是单核CPU GIL不会造成任何影响
3. 由于目前大多数程序都是基于网络的,网络速度对比CPU非常慢,导致即使多核CPU也无法提高效率
4. 对于IO密集型任务,不会有太大的影响
5. 如果没有这把锁,我们程序猿必须自己来解决安全问题
计算密集型任务:进程执行更快
线程:
from multiprocessing import Process
from threading import Thread
import time
# 计算密集型任务
def task():
for i in range(100000000):
1+1
# 进程
if __name__ == '__main__':
strat_time = time.time()
a_list = []
for i in range(5):
# p = Process(target=task)
p = Thread(target=task)
p.start()
a_list.append(p)
for i in a_list:
i.join()
print("共耗时:%s"%(time.time()- strat_time))
共耗时:13.50247573852539
进程:
from multiprocessing import Process
from threading import Thread
import time
# 计算密集型任务
def task():
for i in range(100000000):
1+1
# 进程
if __name__ == '__main__':
strat_time = time.time()
a_list = []
for i in range(5):
p = Process(target=task)
# p = Thread(target=task)
p.start()
a_list.append(p)
for i in a_list:
i.join()
print("共耗时:%s"%(time.time()- strat_time))
共耗时:4.888190746307373
IO密集型:线程执行更快
线程:
from multiprocessing import Process
from threading import Thread
import time
# 计算密集型任务
def task():
for i in range(50):
with open('client_one.py','r',encoding="utf-8")as fr:
fr.read()
# 进程
if __name__ == '__main__':
strat_time = time.time()
a_list = []
for i in range(5):
# p = Process(target=task)
p = Thread(target=task)
p.start()
a_list.append(p)
for i in a_list:
i.join()
print("共耗时:%s"%(time.time()- strat_time))
共耗时:0.019989967346191406
进程:
from multiprocessing import Process
from threading import Thread
import time
# 计算密集型任务
def task():
for i in range(50):
with open('client_one.py','r',encoding="utf-8")as fr:
fr.read()
# 进程
if __name__ == '__main__':
strat_time = time.time()
a_list = []
for i in range(5):
p = Process(target=task)
# p = Thread(target=task)
p.start()
a_list.append(p)
for i in a_list:
i.join()
print("共耗时:%s"%(time.time()- strat_time))
共耗时:0.33380866050720215
自定义锁与GIL的区别
GIL锁住的是解释器级别的数据,比如对象的引用计数,垃圾分代数据等等,具体参考垃圾回收机制详解
自定义锁的是解释器以外的共享资源,例如:硬盘上的文件,控制台
对于程序中自己定义的数据则没有任何的保护效果,这一点在没有介绍GIL前我们就已经知道了,所以当程序中出现了共享自定义的数据时就要自己加锁,如下例:
from threading import Thread,Lock
import time
a = 0
def task():
global a
temp = a
time.sleep(0.01)
a = temp + 1
t1 = Thread(target=task)
t2 = Thread(target=task)
t1.start()
t2.start()
t1.join()
t2.join()
print(a)
过程分析:
- 线程1获得CPU执行权,并获取GIL锁执行代码 ,得到a的值为0后进入睡眠,释放CPU并释放GIL
- 线程2获得CPU执行权,并获取GIL锁执行代码 ,得到a的值为0后进入睡眠,释放CPU并释放GIL
- 线程1睡醒后获得CPU执行权,并获取GIL执行代码 ,将temp的值0+1后赋给a,执行完毕释放CPU并释放GIL
- 线程2睡醒后获得CPU执行权,并获取GIL执行代码 ,将temp的值0+1后赋给a,执行完毕释放CPU并释放GIL,最后a的值也就是1
之所以出现问题是因为两个线程在并发的执行同一段代码,解决方案就是加锁
from threading import Thread,Lock
import time
lock = Lock()
a = 0
def task():
global a
lock.acquire()
temp = a
time.sleep(0.01)
a = temp + 1
lock.release()
t1 = Thread(target=task)
t2 = Thread(target=task)
t1.start()
t2.start()
t1.join()
t2.join()
print(a)
过程分析:
- 线程1获得CPU执行权,并获取GIL锁执行代码 ,得到a的值为0后进入睡眠,释放CPU并释放GIL,不释放lock
- 线程2获得CPU执行权,并获取GIL锁,尝试获取lock失败,无法执行,释放CPU并释放GIL
- 线程1睡醒后获得CPU执行权,并获取GIL继续执行代码 ,将temp的值0+1后赋给a,执行完毕释放CPU释放GIL,释放lock,此时a的值为1
- 线程2获得CPU执行权,获取GIL锁,尝试获取lock成功,执行代码,得到a的值为1后进入睡眠,释放CPU并释放GIL,不释放lock
- 线程2睡醒后获得CPU执行权,获取GIL继续执行代码 ,将temp的值1+1后赋给a,执行完毕释放CPU释放GIL,释放lock,此时a的值为2
并发编程之GIL的更多相关文章
- python并发编程之threading线程(一)
进程是系统进行资源分配最小单元,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.进程在执行过程中拥有独立的内存单元,而多个线程共享内存等资源. 系列文章 py ...
- Python核心技术与实战——十七|Python并发编程之Futures
不论是哪一种语言,并发编程都是一项非常重要的技巧.比如我们上一章用的爬虫,就被广泛用在工业的各个领域.我们每天在各个网站.App上获取的新闻信息,很大一部分都是通过并发编程版本的爬虫获得的. 正确并合 ...
- [转载]并发编程之Operation Queue和GCD
并发编程之Operation Queue http://www.cocoachina.com/applenews/devnews/2013/1210/7506.html 随着移动设备的更新换代,移动设 ...
- Java并发编程之CAS
CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...
- 并发编程之wait()、notify()
前面的并发编程之volatile中我们用程序模拟了一个场景:在main方法中开启两个线程,其中一个线程t1往list里循环添加元素,另一个线程t2监听list中的size,当size等于5时,t2线程 ...
- 并发编程之 Exchanger 源码分析
前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...
- 并发编程之 Condition 源码分析
前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...
- python并发编程之Queue线程、进程、协程通信(五)
单线程.多线程之间.进程之间.协程之间很多时候需要协同完成工作,这个时候它们需要进行通讯.或者说为了解耦,普遍采用Queue,生产消费模式. 系列文章 python并发编程之threading线程(一 ...
- python并发编程之gevent协程(四)
协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...
随机推荐
- 调用 flutter 第三方时间组件
https://pub.flutter-io.cn/packages/flutter_cupertino_date_picker flutter_cupertino_date_picker: ^1.0 ...
- 【PAT甲级】1103 Integer Factorization (30 分)
题意: 输入三个正整数N,K,P(N<=400,K<=N,2<=P<=7),降序输出由K个正整数的P次方和为N的等式,否则输出"Impossible". / ...
- Python中的参数解包:`*`表达式和 `**`表达式
目录 1.参数解包:方法调用中的*表达式和**表达式 2.参数解包:方法定义中的*表达式和**表达式 3.在元组,列表,集合和字典中解包 4.Extended Unpacking:赋值表达式左边的*表 ...
- 91云服务器网络带宽测试,IO测试、全国ping测试
91yun服务器测试一键包介绍 一键包主要是为了让大家快速对服务器的基本状况有一个了解.考虑到天朝的网络出口问题,所以这个一键包更加偏向网络的测试. 影响测试耗时主要是下载,整个测试如果是能跑满100 ...
- C#面向对象三大特性:多态
什么是多态 公司最近为了陶冶情操,养了几种动物(Animal),有猫(Cat).狗(Dog).羊(Sheep),这些动物都有共同的特性,会吃(Eat).会叫(Shout),但是它们吃的不同,叫的也不同 ...
- css中定义变量
css中定义变量 定义变量可分多种情况: 1.定义全局变量 :root { --borderColor: #ccc;} 2.定义某元素下的变量 .look{ --borderColor: #ccc;} ...
- 【MySQL】用户管理及备份
"我们知道我们的最高权限管理者是root用户,它拥有着最高的权限,包括select.update.delete.grant等操作.一般在公司里DBA工程师会创建一个用户和密码,让你去连接数据 ...
- Docker容器CPU限制选项测试
目录 Docker容器CPU限制选项测试 参考 实验环境 --cpu-shares选项 测试 结论 --cpus选项 测试 结论 --cpuset-cpus选项 测试 结论 Docker容器CPU限制 ...
- SQL Server 2014数据库开启远程连接(Windows Server 2016)
1.打开SQL SERVER 配置管理器 2. 设置防火墙的入站规则 3.使用Navicat Premium连接SQL Server
- Java入门笔记 03-面向对象(下)
介绍:除了前面介绍的关于类.对象的基本语法之外,下面继续介绍Java面向对象的特性. 一. 包装类: 从JDK 1.5以后,Java就提供了自动装箱和自动拆箱操作,即: 自动装箱:将一个基本类型的变量 ...