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. 线程1获得CPU执行权,并获取GIL锁执行代码 ,得到a的值为0后进入睡眠,释放CPU并释放GIL
  2. 线程2获得CPU执行权,并获取GIL锁执行代码 ,得到a的值为0后进入睡眠,释放CPU并释放GIL
  3. 线程1睡醒后获得CPU执行权,并获取GIL执行代码 ,将temp的值0+1后赋给a,执行完毕释放CPU并释放GIL
  4. 线程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. 线程1获得CPU执行权,并获取GIL锁执行代码 ,得到a的值为0后进入睡眠,释放CPU并释放GIL,不释放lock
  2. 线程2获得CPU执行权,并获取GIL锁,尝试获取lock失败,无法执行,释放CPU并释放GIL
  3. 线程1睡醒后获得CPU执行权,并获取GIL继续执行代码 ,将temp的值0+1后赋给a,执行完毕释放CPU释放GIL,释放lock,此时a的值为1
  4. 线程2获得CPU执行权,获取GIL锁,尝试获取lock成功,执行代码,得到a的值为1后进入睡眠,释放CPU并释放GIL,不释放lock
  5. 线程2睡醒后获得CPU执行权,获取GIL继续执行代码 ,将temp的值1+1后赋给a,执行完毕释放CPU释放GIL,释放lock,此时a的值为2

并发编程之GIL的更多相关文章

  1. python并发编程之threading线程(一)

    进程是系统进行资源分配最小单元,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.进程在执行过程中拥有独立的内存单元,而多个线程共享内存等资源. 系列文章 py ...

  2. Python核心技术与实战——十七|Python并发编程之Futures

    不论是哪一种语言,并发编程都是一项非常重要的技巧.比如我们上一章用的爬虫,就被广泛用在工业的各个领域.我们每天在各个网站.App上获取的新闻信息,很大一部分都是通过并发编程版本的爬虫获得的. 正确并合 ...

  3. [转载]并发编程之Operation Queue和GCD

    并发编程之Operation Queue http://www.cocoachina.com/applenews/devnews/2013/1210/7506.html 随着移动设备的更新换代,移动设 ...

  4. Java并发编程之CAS

    CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...

  5. 并发编程之wait()、notify()

    前面的并发编程之volatile中我们用程序模拟了一个场景:在main方法中开启两个线程,其中一个线程t1往list里循环添加元素,另一个线程t2监听list中的size,当size等于5时,t2线程 ...

  6. 并发编程之 Exchanger 源码分析

    前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...

  7. 并发编程之 Condition 源码分析

    前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...

  8. python并发编程之Queue线程、进程、协程通信(五)

    单线程.多线程之间.进程之间.协程之间很多时候需要协同完成工作,这个时候它们需要进行通讯.或者说为了解耦,普遍采用Queue,生产消费模式. 系列文章 python并发编程之threading线程(一 ...

  9. python并发编程之gevent协程(四)

    协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...

随机推荐

  1. Codeforces Global Round 6D(VECTOR<ARRAY<INT,3> >)

    一个人只要存在债务关系,那么他的债务可以和这整个债务关系网中任何人连边,和他当初借出或欠下的人没有关系.只需要记录他的债务值即可. #define HAVE_STRUCT_TIMESPEC #incl ...

  2. [转]使用HttpOnly提升Cookie安全性

    原文:https://www.cnblogs.com/zlhff/p/5477943.html 在介绍HttpOnly之前,我想跟大家聊聊Cookie及XSS. 随着B/S的普及,我们平时上网都是依赖 ...

  3. 每天进步一点点------Sobel算子(3)基于彩色图像边缘差分的运动目标检测算法

    摘  要: 针对目前常用的运动目标提取易受到噪声影响.易出现阴影和误检漏检等情况,提出了一种基于Sobel算子的彩色边缘图像检测和帧差分相结合的检测方法.首先用Sobel算子提取视频流中连续4帧图像的 ...

  4. MySQL存储引擎优化

    如何在两种存储引擎中进行选择? ① 是否有事务操作?有,InnoDB. ②是否存储并发修改?有,InnoDB. ③是否追求快速查询,且数据修改较少?是,MyISAM. ④是否使用全文索引?如果不引用第 ...

  5. bootstrap创建带遮罩层的进度条

    <div class="modal fade" id="loadingModal"> <div style="width: 200p ...

  6. 2019,.Net开发者的高光时刻

    随着微软发布的一系列关于Windows..net和C#的公告,.Net开发者将在2019年,迎来自己的高光时刻,毕竟“世界上只有少数几种语言是多功能的,而没有一个像C#那样干净整洁.” 一.现在学C# ...

  7. Kubernetes中网络相关知识

    流量转发和桥接 Kubernetes的核心是依靠Netfilter内核模块来设置低级别的集群IP负载均衡.需要两个关键的模块:IP转发和桥接 IP转发(IP Forward) IP forward 是 ...

  8. mysql将主键序号置为1

    ALTER TABLE tongji_article_ctr AUTO_INCREMENT=

  9. pipreqs (找当前项目依赖的包)

    pipreqs pipreqs可以帮你找到当前项目的所有组件及其版本.就是当别人给你一个程序的时候,你要在自己电脑上运行起来,就需要安装程序所依赖的组件,总不能自己一个一个找吧. # 安装 pip3 ...

  10. OGG在windows环境下字符集的配置

    windows环境下不配置字符集(默认使用windows自己的字符集),从linux等系统同步过来的表中如果含有中文字符列将显示为乱码,被ogg误认为虚拟列,从而导致进程abend. 设置ogg进程在 ...