一:Python中的GIL锁

  • 简介
在Python中,可以通过多进程、多线程和多协程来实现多任务。
在多线程的实现过程中,为了避免出现资源竞争问题,可以使用互斥锁来使线程同步(按顺序)执行。
但是,其实Python的CPython(C语言实现的)解释器上有一把GIL锁,也就是说Python的程序是处于一个解释器锁的环境中的。
1.GIL介绍
GIL (Global Interperter Lock) 称作全局解释器锁。
GIL并不是Python语言的特性,它是在实现Python解释器时引用的一个概念。GIL只在CPython解释器上存在。
不过,在Python的解释器中,使用最多的都是CPython解释器,所以我们不可避免的会遇到GIL。
在使用互斥锁解决代码中的资源竞争问题时,当一个线程执行时,会将全局共享的资源上锁,当线程执行完成后,将锁解开,释放资源,其他线程才能够使用。
GIL的作用与互斥锁的作用相似,是为了解决解释器中多个线程资源竞争的问题。 GIL锁:全局解释器锁,在解释器之上的一把大锁,线程必须获得这把锁,才能执行,只针对与cpython解释器

2.GIL的作用
1.Python中的多线程被称为“伪多线程”,因为无论如何,都逃不过GIL解释器锁。

2.因为GIL的存在,在Python中同一时刻有且只有一个线程会执行。

3.因为线程是存在于进程中的,线程是CPU调度和分派的基本单位,Python中的多线程由于GIL锁的存在无法利用多核 CPU。

4.GIL在程序中有IO操作时才切换到其他线程,所以Python中的多线程不适合计算密集型的程序,只适合IO密集型的程序。

既然GIL的存在使程序无法充分利用CPU进行运算,那么在IO密集型程序中为什么适合使用呢?

通常,程序分为两种,一种是计算密集型程序,另一种叫作IO密集型程序。

大部分的程序在运行时,都需要大量IO操作,比如网络数据的收发,大文件的读写,这样的程序称为IO密集型程序。

IO密集型程序在运行时,需要大量的时间进行等待,如果IO操作不完成,程序无法执行后面的操作,一直处于等待状态,导致CPU空闲。

由于GIL的存在,同一时刻只能有一个线程执行,在程序进行IO操作时,CPU实际并没有做任何工作,程序执行效率非常低。

为了提高CPU的使用率,Python解释在程序执行IO等待时,会释放GIL锁,让其它线程执行,提高Python程序的执行效率。

所以,GIL对于IO密集型的影响很小,多线程适合用来做IO密集型的程序。
3.cpython
在Cpython中GIL全局解释器锁其实也是一把互斥锁,主要用于阻止同一个进程下的多个线程同时被运行(python的多线程无法使用多核优势)

GIL肯定存在于CPython解释器中 主要原因就在于Cpython解释器的内存管理不是线程安全的
4.内存管理>>>垃圾回收机制
	引用计数
标记清除
分代回收

二:全局解释器锁GIL

python设计之初还无多核概念,是单核。
python需要做垃圾回收 如何回收:垃圾回收线程,启动并进行垃圾回收
垃圾回收线程在回收垃圾之时,若有多条线程,比如现已有一条A线程,此时启动垃圾回收线程,但在销毁变量时,线程A还在使用变量,垃圾回收机制便无法执行。所以垃圾回收机制一旦启动就需要一把锁。这把锁就叫做**全局解释器锁GIL**,垃圾回收线程获得此锁之后,其他线程就不能运行,那么在回收变量的时候就不会出现并发安全问题。 垃圾回收线程在运行的时候,其他线程均处于阻塞状态。
只有拿到GIL锁的线程,才能运行。 同一个时刻,在一个进程中可以开多个线程,但只能有一条线程在执行
起初设置GIL仅仅是为了做垃圾回收,在当时还只有单核,并无多核。所以开了多条线程也不会被多个CPU调动执行,因为只有单核。线程拿到这把锁才能运行。在当时是没有问题,因为只有一个CPU。但是随着多核CPU的出现,假设电脑是四核,一个进程四条线程,理论一个线程一核,但是Python不行,在一个线程中开四条线程并不会被四个核运行,同一时刻只能有一个线程在一个核运行,就是因为GIL原因。 因此python不能利用多核优势(cpython解释器的问题)。
1.GIL特点
1.GIL是Cpython解释器的特点2.python同一个进程内的多个线程无法利用多核优势(不能并行但是可以并发)3.同一个进程内的多个线程要想运行必须先抢GIL锁4.所有的解释型语言几乎都无法实现同一个进程下的多个线程同时被运行

三:计算密集型与IO密集型程序

  • 1.计算密集型
计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
直言比喻 比如造原子弹 需要大量计算这种
  • 2.IO密集型
大部分的程序在运行时,都需要大量IO操作,比如网络数据的收发,大文件的读写,这样的程序称为IO密集型程序。

IO密集型程序在运行时,需要大量的时间进行等待,如果IO操作不完成,程序无法执行后面的操作,一直处于等待状态,导致CPU空闲。

由于GIL的存在,同一时刻只能有一个线程执行,在程序进行IO操作时,CPU实际并没有做任何工作,程序执行效率非常低。

为了提高CPU的使用率,Python解释在程序执行IO等待时,会释放GIL锁,让其它线程执行,提高Python程序的执行效率。

所以,GIL对于IO密集型的影响很小,多线程适合用来做IO密集型的程序。

四:什么情况下cpu会切换线程?

  • 1.长时间占用cpu

  • 2.程序io操作

  • 3.阻塞状态时

五:验证GIL的存在

from threading import Thread
import time
m = 100
def test():
global m
tmp = m
tmp -= 1
m = tmp
for i in range(100):
t = Thread(target=test)
t.start()
time.sleep(3)
print(m)
  • 结果:
0
1.总结
同一个进程下的多个线程虽然有GIL的存在不会出现并行的效果
但是如果线程内有IO操作还是会造成数据的错乱 这个时候需要我们额外的添加互斥锁

六:死锁现象

1.是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁

2.在多道程序系统中,由于多个进程的并发执行,改善了系统资源的利用率并提高了系统的处理能力。然而,多个进程的并发执行也带来了新的问题——死锁。所谓死锁是指多个进程因竞争资源而造成的一种僵局,若无外力作用,这些进程都将无法向前推进。​
  • 代码实现死锁现象
from threading import Thread, Lock
import time A = Lock()
B = Lock() class MyThread(Thread):
def run(self):
self.func1()
self.func2() def func1(self):
A.acquire()
print('%s 抢到了A锁' % self.name) # current_thread().name 获取线程名称
B.acquire()
print('%s 抢到了B锁' % self.name)
time.sleep(1)
B.release()
print('%s 释放了B锁' % self.name)
A.release()
print('%s 释放了A锁' % self.name) def func2(self):
B.acquire()
print('%s 抢到了B锁' % self.name)
A.acquire()
print('%s 抢到了A锁' % self.name)
A.release()
print('%s 释放了A锁' % self.name)
B.release()
print('%s 释放了B锁' % self.name) for i in range(10):
obj = MyThread()
obj.start()

"""就算知道锁的特性及使用方式 也不要轻易的使用 因为容易产生死锁现象"""

1.出现死锁的原因
线程1 先开始执行func1,分布拿到AB锁,然后释放
线程1 先执行func2,先拿到了B锁,开始sleep
线程2 先拿到了A锁
这时候形成了僵局,线程2想要线程1手里的B锁,线程1想要线程2里的A锁。

2.解决死锁
解决死锁问题  RLock:可重入,可以重复acquire,获得几次,就要释放几次

可以被连续的acquire和release
但是只能被第一个抢到这把锁指向上述操作
它的内部有一个计数器 每acquire一次计数加1 每release一次计数减1
只要计数不为零 那么其他人都无法抢到该锁
计数器 模块 RLOOK
  • 代码实现解决死锁
from threading import Thread, RLock
import time # 指向同一个内存地址
A = RLock()
B = A class MyThread(Thread):
def run(self):
self.func1()
self.func2() def func1(self):
A.acquire()
print('%s 抢到了A锁' % self.name) # current_thread().name 获取线程名称
B.acquire()
print('%s 抢到了B锁' % self.name)
time.sleep(1)
B.release()
print('%s 释放了B锁' % self.name)
A.release()
print('%s 释放了A锁' % self.name) def func2(self):
B.acquire()
print('%s 抢到了B锁' % self.name)
A.acquire()
print('%s 抢到了A锁' % self.name)
A.release()
print('%s 释放了A锁' % self.name)
B.release()
print('%s 释放了B锁' % self.name) for i in range(10):
obj = MyThread()
obj.start()

七:python多线程是否没用

1. 是否有用需要看情况而定(程序的类型)
  • IO密集型

    eg:四个任务 每个任务耗时10s

    开设多进程没有太大的优势 42s+

    遇到IO就需要切换 并且开设进程还需要申请内存空间和拷贝代码

    开设多线程有优势

    不需要消耗额外的资源 2s+
  • 计算密集型

    eg:四个任务 每个任务耗时10s

    计算密集型任务的特点是要进行大量的计算

    开设多进程可以利用多核优势 5s+

    开设多线程无法利用多核优势 23s+
2.多进程结合多线程
可以处理计算密集型与IO密集型
3."""IO密集型"""
# from multiprocessing import Process
# from threading import Thread
# import threading
# import os,time
# def work():
# time.sleep(2)
#
#
# if __name__ == '__main__':
# l=[]
# print(os.cpu_count()) #本机为6核
# start=time.time()
# for i in range(400):
# # p=Process(target=work) #耗时42.54s多,大部分时间耗费在创建进程上
# p=Thread(target=work) #耗时2.08s多
# l.append(p)
# p.start()
# for p in l:
# p.join()
# stop=time.time()
# print('run time is %s' %(stop-start))

4."""计算密集型"""
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()) # 本机为6核
start=time.time()
for i in range(6):
# p=Process(target=work) #耗时5.35s多
p=Thread(target=work) #耗时23.37s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))

python全局解释器GIL锁(-死锁)的更多相关文章

  1. python全局解释器GIL

    1.什么是进程: 进程是竞争计算机资源的基本单位.对于单核CPU来讲,同一时间只能有一个进程在运行,所以当我们开启多个应用时,操作系统需要根据进程调度算法去在不同的应用程序之间切换,而不同的进程之间切 ...

  2. python全局解释器锁(GIL)

    文章作者:卢钧轶(cenalulu) 本文原文地址:http://cenalulu.github.io/python/gil-in-python/ ,对文章做了适当的修改,加入了一些自己的理解. CP ...

  3. Python全局解释器锁 -- GIL

    首先强调背景: 1.GIL是什么?GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定. 2.每个CPU在同一时间只能 ...

  4. Python核心技术与实战——十九|一起看看Python全局解释器锁GIL

    我们在前面的几节课里讲了Python的并发编程的特性,也了解了多线程编程.事实上,Python的多线程有一个非常重要的话题——GIL(Global Interpreter Lock).我们今天就来讲一 ...

  5. Python Threading 线程/互斥锁/死锁/GIL锁

    导入线程包 import threading 准备函数线程,传参数 t1 = threading.Thread(target=func,args=(args,)) 类继承线程,创建线程对象 class ...

  6. Python全局解释器锁

    超过十年以上,没有比解释器全局锁(GIL)让Python新手和专家更有挫折感或者更有好奇心.    Python的底层 要理解GIL的含义,我们需要从Python的基础讲起.像C++这样的语言是编译型 ...

  7. Python中的GIL锁

    在Python中,可以通过多进程.多线程和多协程来实现多任务. 在多线程的实现过程中,为了避免出现资源竞争问题,可以使用互斥锁来使线程同步(按顺序)执行. 但是,其实Python的CPython(C语 ...

  8. Python之路(第四十三篇)线程的生命周期、全局解释器锁

    一.线程的生命周期(新建.就绪.运行.阻塞和死亡) 当线程被创建并启动以后,它既不是一启动就进入执行状态的,也不是一直处于执行状态的,在线程的生命周期中,它要经过新建(new).就绪(Ready).运 ...

  9. day34 GIL锁 线程队列 线程池

    一.Gil锁(Global Interpreter Lock) python全局解释器锁,有了这个锁的存在,python解释器在同一时间内只能让一个进程中的一个线程去执行,这样python的多线程就无 ...

随机推荐

  1. c++内存分布之纯虚函数

    关于 本文演示环境:VS2017+32位程序. 纯虚函数是一种特殊的虚函数.可以预测到:虚函数的结论同样适用纯虚函数,但是纯虚函数是一种特殊的存在,还是看看实际结果. 代码写的不够规范: 因为任何带虚 ...

  2. See you~(hdu1892)

    See you~ Time Limit: 5000/3000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Total Su ...

  3. Rikka with Graph(hdu5631)

    Rikka with Graph  Accepts: 123  Submissions: 525  Time Limit: 2000/1000 MS (Java/Others)  Memory Lim ...

  4. Docker 与 K8S学习笔记(六)—— 容器的资源限制

    我们在启动Docker容器时,默认情况下容器所使用的资源是没有限制的,这样就会存在部分特别耗资源的容器会占用大量系统资源,从而导致其他容器甚至整个服务器性能降低,为此,Docker提供了一系列参数方便 ...

  5. 分布式系统(二)——GFS

    分布式存储系统的难点 在存储系统中,为了获得巨大的性能加成,一个很自然的想法就是采用分片(sharding),将数据分割存储到多台服务器上,这样获得了更大的存储容量,而且可以并行地从多台服务器读取数据 ...

  6. 初识JavaScript变量

    一.什么是变量? 变量即变化的量,在JS中变量是松散类型的,可以用来保存任何数据类型.把数据取个名字,放在内存中,就称之为变量! 通过变量名可以取到对应数据 二.为什么使用变量? 程序:代码的集合,一 ...

  7. [error]Flask Address already in use

    在Python的Flask框架下Address already in use [地址已在使用中] 出现这种错误提示, 说明你已经有一个流程绑定到默认端口(5000).如果您之前已经运行过相同的模块,则 ...

  8. 前后端java+vue 实现rsa 加解密与摘要签名算法

    RSA 加密.解密.签名.验签.摘要,前后端java+vue联调测试通过 直接上代码 // 注意:加密密文与签名都是唯一的,不会变化.// 注意:vue 端密钥都要带pem格式.java 不要带pem ...

  9. Ubuntu复习笔记-认识Linux

    本次复习基于\(Ubuntu20.04\)的发行版进行总结,目的是更好记录自己学习的\(Linux\). 认识Linux 学习\(Linux\)之前,需要搞懂几个概念,\(Linux\)桌面操作系统与 ...

  10. svn创建多个版本库

    mkdir /pangbing cd /pangbing/ svnadmin create 1 svnadmin create 2 svnadmin create3 启动时候这样启动 svnserve ...