GIL、死锁与递归锁
一、互斥锁
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,竞争带来的结果就是错乱,如何控制,就是加锁处理(即局部实行串行)。
模拟抢票实例:
- from multiprocessing import Process,Lock
- import json,os,time,random
- def search():
- with open('db.txt',encoding='utf-8')as f:
- dict = json.load(f)
- print('%s 剩余票数 %s'%(os.getpid(),dict['count']))
- def get():
- with open('db.txt',encoding='utf-8') as reaf_f:
- dic = json.load(reaf_f)
- if dic['count']>0:
- dic['count'] -= 1
- time.sleep(random.randint(1,3)) # 模拟手速,网速
- with open('db.txt','w',encoding='utf-8') as write_f:
- json.dump(dic,write_f)
- print('%s 抢票成功' %os.getpid())
- else:
- print('剩余票数为%s,购票失败'%dic['count'])
- def task(mutex):
- search() # 20个人都可以并发的查询票数
- mutex.acquire() # 加锁
- get() #通过锁,查询到结果的20人通过竞争逐一买票。前一个释放锁后后一个才可以进入,即串行
- mutex.release() # 解锁
- if __name__ == '__main__':
- mutex = Lock()
- for i in range(20): # 20个人抢票
- p = Process(target=task,args=(mutex,))
- p.start()
二、GIL互斥锁(保护解释器级别)
在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。为什么会这样呢?
1、python程序执行顺序
如执行test.py程序,都会开启python一个进程,代码中包含多个线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有线程都运行在这一个进程内,毫无疑问。
执行流程:多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行,解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法,就是加锁处理,而这把锁就是GIL,保证python解释器同一时间只能执行一个任务的代码。
2、GIL锁介绍
GIL保护的是解释器级的数据,保护用户自己的数据则需要自己加锁处理,如下图主进程中开启了两个线程,由于线程1先拿到解释器的GIL锁,故向cpu传送指令执行此线程,若此线程在执行过程遇到IO阻塞,被解释器强行要求释放GIL锁,线程2进入解释器执行相应代码,等线程1再次拿到解释器的权限时,继续执行其剩余程序。
3.多线程与GIL
有了GIL存在,同一时刻同一进程中只有一个线程被执行。但是多线程仍然存在它的意义,解释如下:
案例:我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
方案一:开启四个进程,方案二:一个进程下,开启四个线程。
#单核情况下,分析结果:
如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜
如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜
#多核情况下,分析结果:
如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜
如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜
结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。
- 应用(总结):
- 多线程用于IO密集型,如socket,爬虫,web
- 多进程用于计算密集型,如金融分析
- 1. 每个cpython进程内都有一个GIL
- 2. GIL导致同一进程内多个进程同一时间只能有一个运行
- 3. 之所以有GIL,是因为Cpython的内存管理不是线程安全的
- 4. 对于计算密集型用多进程,多IO密集型用多线程
(1)计算密集型实例
多线程:
- from threading import Thread
- import time
- def work():
- res=0
- for i in range(100000000):
- res*=i
- if __name__ == '__main__':
- start=time.time()
- result=[]
- for i in range(4):#因为4核,所以开启4个进程
- p=Thread(target=work)
- result.append(p)
- p.start()
- for p in result:
- p.join()
- end=time.time()
- print(end-start) #24.468851566314697
多进程:
- from multiprocessing import Process
- import time
- def work():
- res=0
- for i in range(100000000):
- res*=i
- if __name__ == '__main__':
- start=time.time()
- result=[]
- for i in range(4):#因为4核,所以开启4个进程
- p=Process(target=work)
- result.append(p)
- p.start()
- for p in result:
- p.join()
- end=time.time()
- print(end-start) #15.74721097946167
分析:对于多进程情况,所开4个进程分别在4个cpu上同时进行执行,所花时间为开启进程时间和单个进程运行时间的总和,对于多线程情况,其都在竞争解释器权限,排队进行执行代码,所化时间为四个线程的总和。多线程花费时间未与多进程花费时间呈4倍关系是因为开启进程开销比开启线程开销大很多。
(2)IO密集型实例
多线程:
- from threading import Thread
- import time
- def work():
- time.sleep(2)
- if __name__ == '__main__':
- start=time.time()
- result=[]
- for i in range(400):
- p=Thread(target=work)
- result.append(p)
- p.start()
- for p in result:
- p.join()
- end=time.time()
- print(end-start) #2.049804925918579
多进程
- from multiprocessing import Process
- import time
- def work():
- time.sleep(2)
- if __name__ == '__main__':
- start = time.time()
- result = []
- for i in range(400):
- p = Process(target=work)
- result.append(p)
- p.start()
- for p in result:
- p.join()
- end = time.time()
- print(end - start) # 27.082503080368042
分析:对于多线程情况,线程遇到阻塞,便会切换到另个线程,所化总时间就为花时间最长的单个线程的时间,对于多进程情况,同时开启这么多进程会花费很多时间,其次单个进程执行遇到阻塞时,若没有其他任务也会继续等待阻塞结束。
三、死锁与递归锁
1、死锁现象
是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁:
- from threading import Thread,Lock
- import time
- mutexA=Lock()
- mutexB=Lock()
- class MyThread(Thread):
- def run(self):
- self.func1()
- self.func2()
- def func1(self):
- mutexA.acquire()
- print('\033[41m%s 拿到A锁\033[0m' %self.name)
- mutexB.acquire()
- print('\033[42m%s 拿到B锁\033[0m' %self.name)
- mutexB.release()
- mutexA.release()
- def func2(self):
- mutexB.acquire()
- print('\033[43m%s 拿到B锁\033[0m' %self.name)
- time.sleep(2)
- mutexA.acquire()
- print('\033[44m%s 拿到A锁\033[0m' %self.name)
- mutexA.release()
- mutexB.release()
- if __name__ == '__main__':
- for i in range(10):
- t=MyThread()
- t.start()
- '''
- Thread-1 拿到A锁
- Thread-1 拿到B锁
- Thread-1 拿到B锁
- Thread-2 拿到A锁
- 然后就卡住,死锁了
- ''
2、递归锁 (可以acquire多次)
为解决上述死锁现象,出现了递归锁的概念:在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
- from threading import Thread,RLock
- import time
- mutexA=mutexB=RLock()
- class MyThread(Thread):
- def run(self):
- self.func1()
- self.func2()
- def func1(self):
- mutexA.acquire()
- print('%s 拿到A锁' %self.name)
- mutexB.acquire()
- print('%s 拿到B锁' %self.name)
- mutexB.release()
- mutexA.release()
- def func2(self):
- mutexB.acquire()
- print('%s 拿到B锁' %self.name)
- time.sleep(2)
- mutexA.acquire()
- print('%s 拿到A锁' %self.name)
- mutexA.release()
- mutexB.release()
- if __name__ == '__main__':
- for i in range(10):
- t=MyThread()
- t.start()
分析:一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止。
GIL、死锁与递归锁的更多相关文章
- 10 并发编程-(线程)-GIL全局解释器锁&死锁与递归锁
一.GIL全局解释器锁 1.引子 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势 首先需要明确的一点是GIL并不是Python的特性,它是在实现Pyt ...
- GIL锁、死锁、递归锁、定时器
GIL (Global Interpreter Lock) 锁 '''定义:In CPython, the global interpreter lock, or GIL, is a mutex th ...
- GIL全局解释器锁、死锁、递归锁、线程队列
目录 GIL全局解释锁 多线程的作用 测试计算密集型 IO密集型 死锁现象 递归锁 信号量(了解) 线程队列 GIL全局解释锁 GIL本质上是一个互斥锁. GIL是为了阻止同一个进程内多个进程同时执行 ...
- GIL全局解释器锁-死锁与递归锁-信号量-event事件
一.全局解释器锁GIL: 官方的解释:掌握概念为主 """ In CPython, the global interpreter lock, or GIL, is a m ...
- [并发编程 - 多线程:信号量、死锁与递归锁、时间Event、定时器Timer、线程队列、GIL锁]
[并发编程 - 多线程:信号量.死锁与递归锁.时间Event.定时器Timer.线程队列.GIL锁] 信号量 信号量Semaphore:管理一个内置的计数器 每当调用acquire()时内置计数器-1 ...
- 26 python 初学(线程、同步锁、死锁和递归锁)
参考博客: www.cnblogs.com/yuanchenqi/articles/5733873.html 并发:一段时间内做一些事情 并行:同时做多件事情 线程是操作系统能够进行运算调度的基本单位 ...
- Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures
参考博客: https://www.cnblogs.com/xiao987334176/p/9046028.html 线程简述 什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线 ...
- python 全栈开发,Day42(Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures)
昨日内容回顾 线程什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线程是什么关系? 线程是在进程中的 一个执行单位 多进程 本质上开启的这个进程里就有一个线程 多线程 单纯的在当 ...
- Python并发编程-进程 线程 同步锁 线程死锁和递归锁
进程是最小的资源单位,线程是最小的执行单位 一.进程 进程:就是一个程序在一个数据集上的一次动态执行过程. 进程由三部分组成: 1.程序:我们编写的程序用来描述进程要完成哪些功能以及如何完成 2.数据 ...
随机推荐
- windo form 窗体布局方式
DataGridView:显示数据表后台数据绑定:List<xxx> list = new List<xxx>();dataGridView1.DataSource = lis ...
- 服务端Job调度程序
GIT地址:https://github.com/youbl/PlanServer
- jQuery带动画的弹出对话框
在线演示 本地下载
- 20145201 《Java程序设计》第二周学习总结
20145201 <Java程序设计>第二周学习总结 教材学习内容总结 本周学习了课本第三章内容,即JAVA基础语法. 3.1 类型.变量与运算符 基本类型:在java中基本类型主要可区分 ...
- centos 6的LAMP一键安装包(可选择/升级版本)
安装步骤 事前准备(安装 wget.screen.unzip,创建 screen 会话) yum -y install wget screen git git clone 并赋予脚本执行权限 git ...
- Linux下MySQL 5.6.24的编译安装与部署
MySQL 5.6正式版发布了,相对于5.5版本作出了不少改进,其源码安装配置方式也有所变化,本文根据实际操作,不断尝试,精确还原了安装的具体步骤. 在Linux下安装MySQL前,先确认卸载系统自带 ...
- 蓝屏代码大全 & 蓝屏全攻略
转载自http://diybbs.zol.com.cn/15/86_141447.html 一.蓝屏含义 1.故障检查信息 ***STOP 0x0000001E(0xC0000005,0xFDE38A ...
- LeetCode——Hamming Distance
LeetCode--Hamming Distance Question The Hamming distance between two integers is the number of posit ...
- 导出成可运行jar包时所遇问题的解决办法 - 转载
Could not find main method from given launch configuration 当我把我的Java工程导出为可运行的jar包时,遇到了“Could not fin ...
- secureCRT7.3.4的破解与安装
1-9为 SecureCRT 7.3.4 安装图解:10-13是 SecureCRT 7.3.4 破解图解,心急的朋友可以直接向下拉. 以下是百度百科对 SecureCRT 的介绍: SecureCR ...