GIL与普通互斥锁区别,死锁现象,信号量,event事件,进程池与线程池,协程
GIL与普通互斥锁区别
GIL锁和互斥锁的异同点
相同:
都是为了解决解释器中多个线程资源竞争的问题
异:
1.互斥锁是Python代码层面的锁,解决Python程序中多线程共享资源的问题(线程数据共共享,当各个线程访问数据资源时会出现竞争状态,造成数据混乱);
2.GIL是Python解释层面的锁,解决解释器中多个线程的竞争资源问题(多个子线程在系统资源竞争是,都在等待对象某个部分资源解除占用状态,结果谁也不愿意先解锁,然后互相等着,程序无法执行下去)。
GIL对程序的影响:
1.Python中同一时刻有且只有一个线程会执行;
2.Python中的多个线程由于GIL锁的存在无法利用多核CPU;
3.Python中的多线程不适合计算机密集型的程序;
4.如果程序需要大量的计算,利用多核CPU资源,可以使用多进程来解决。
IO密集型和计算密集型(CPU密集型)
计算密集型和IO密集型的区别
IO 密集型:系统运作,大部分的状况是CPU 在等I/O (硬盘/内存)的读/写。
CPU 密集型(计算密集型):大部份时间用来做计算、逻辑判断等CPU 动作的程序称之CPU 密集型(计算密集型)。
(CPU密集型)计算密集型任务的特点:
要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。
这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任
务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率
很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。
IO密集型任务的特点:
涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在
等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越
高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言
替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是
开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。
主要使用场景:
多进程适合在CPU 密集型操作(cpu 操作指令比较多,如位数多的浮点运算)。
多线程适合在IO 密集型操作(读写数据操作较多的,比如爬虫)。
单个CPU
多个IO密集型任务
多进程:浪费资源 无法利用多个CPU
多线程:节省资源 切换+保存状态
多个计算密集型任务
多进程:耗时更长 创建进程的消耗+切换消耗
多线程:耗时较短 切换消耗
多个CPU
多个IO密集型任务
多进程:浪费资源 多个CPU无用武之地
多线程:节省资源 切换+保存状态
多个计算密集型任务
多进程:利用多核 速度更快
多线程:速度较慢
死锁现象
开发过程中使用线程,在线程间共享多个资源的时候,
如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
尽管死锁很少发生,但一旦发生就会造成应用的停止响应,程序不做任何事情。
避免死锁:
解决:
1.重构代码
2.添加超时释放锁
添加超时释放锁
from threading import Thread,Lock
import time
lockA = Lock()
lockB = Lock()
#自定义线程
class MyThread1(Thread):
#不论进程还是线程重写的都是run方法
def run(self):
if lockA.acquire():#如果可以获取到锁则返回True
print(self.name +'A锁')
time.sleep(0.1)
if lockB.acquire(timeout=3):#在acquire函数中阻塞,一直等待锁,不能往下运行了
#如果加上超时则表示退出acquire,继续往下执行把a锁释放了
print(self.name +"A锁+B锁")
lockB.release()
lockA.release()
#自定义线程
class MyThread2(Thread):
#不论进程还是线程重写的都是run方法
def run(self):
if lockB.acquire():#如果可以获取到锁则返回True
print(self.name +'B锁')
time.sleep(0.1)
if lockA.acquire(timeout=3):
print(self.name +"A锁+B锁")
lockA.release()
lockB.release()
def main():
pass
if __name__ == "__main__":
#若不加上超时,则会一直不能进入A锁+B锁情况。
#造成线程1,2一直死等
MyThread1().start()
MyThread2().start()
信号量
信号量:是最古老的同步原语之一,是一个计数器
当资源释放时计数器就会递增,当资源申请时计数器就会递减。可以认为信号量就代表着资源是否可用
以停车场的运作为例。
假设停车场只有三个车位,开始三个车位都是空的。这时同时来了五辆车,看门人开闸允许其中三辆直
接进入,剩下的车则必须在入口等待,后续来的车也在入口处等待。这时一辆车想离开停车场,告知看门人,
打开闸门放他出去,看门人看了看空车位数量,然后看门人才让外面的一辆车进去。如果又离开两辆,则又可
以放入两辆,如此往复。
在这个停车场系统中,车位是公共资源,每辆车好比一个线程,
看门人起的就是信号量的作用。
python里面的信号量semaphore
python统一了所有的命名,使用与线程锁(互斥锁)同样的方法命名消耗和释放资源
acquire方法 消耗资源加1 空车位减1
release方法 释放资源加1 空车位加1
创建Semaphore类实例才可以使用信号量semaphore
通过该类的构造方法传入计数器的最大值空车位总数
from threading import Semaphore
Max =3
s =Semaphore(Max)
print(s._value)#输出计数器的值
s.acquire()#消耗1个资源
s.acquire()
s.acquire()
print(s._value)#输出计数器的值 这时输出的是0 也可以表示为False 当设定条件时,可以使用False来作为条件判断
# s.acquire()#已经没有资源了,再减就一直处于等待状态,除非设定了其他资源在执行完毕后并释放 这样才能继续消耗
s.release()#释放1个资源
s.release()
s.release()
# s.release()#已超过资源设定的最大值了,再加就抛出异常 相当于停车场一共才3个车位,怎么会显示有4个车位呢?
print(s._value)#输出计数器的值
event事件
"""
子线程的运行可以由其他子线程决定!!!
"""
Event几种方法:
event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。
例子:红绿灯
from threading import Thread,Event
import time
event=Event() # 创建一个红绿灯
def light():
print('红灯正亮着')
time.sleep(3)
event.set() #绿灯亮
def car(name):
print('车%s正在等绿灯' %name)
event.wait() #等灯绿 此时event为False,直到event.set()将其值设置为True,才会继续运行.
print('车%s通行' %name)
if __name__ == '__main__':
# 红绿灯
t1=Thread(target=light)
t1.start()
# 车
for i in range(10):
t=Thread(target=car,args=(i,))
t.start()
进程池与线程池
池的概念
池是用来保证计算机硬件安全的情况下最大限度的利用计算机
它降低了程序的运行效率但是保证了计算机硬件的安全从而让你写的程序能够正常运行
'''
无论是开设进程也好还是开设线程也好 都需要消耗资源
只不过开设线程的消耗比开设进程的稍微小一点而已
我们是不可能做到无限制的开设进程和线程的 因为计算机硬件的资源更不上!!!
硬件的开发速度远远赶不上软件
我们的宗旨应该是在保证计算机硬件能够正常工作的情况下最大限度的利用它
'''
进程池/线程池类方法
'''
进程池和线程池的使用方法完全一致,只是它们的类名不同而已,另外要注意区分任务类型,对于IO多计算
少的任务使用线程池,对于计算多IO少的任务使用进程池。
'''
concurrent.futures模块是CPython官方提供的进程池/线程池模块,ThreadPoolExecutor类封装了线程
池的相关方法、ProcessPoolExecutor类封装了进程池的相关方法。 以下三个是进程池类和线程池类共有的
方法,用法完全一致:
----------------------------------------------------------------------------------------
submit方法,submit(fn, /, *args, **kwargs):
功能:
调度可调用对象fn,以 fn(*args **kwargs)方式执行并返回Future实例(它可以返回调用对象fn返回的结果)。返回Future类的实例。
参数:
fn:可调用对象。
/:指定后续的args不能以名字传参,只能通过位置传参。
*args:使用args变量接收用户传的所有位置参数。
**kwargs:使用kwargs变量接收用户传的所有关键字参数。
----------------------------------------------------------------------------------------
map方法,map(func, *iterables, timeout=None, chunksize=1):
功能:
调度可调用对象func,以 func(遍历iterables中的元素)方式同时执行并返回func的结果到迭代器。
返回迭代器,遍历该迭代器可得到所以func的返回值。
参数:
func:指可调用对象。
iterables:指可迭代对象,map会遍历iterables并使用它的每一项元素作为func的参数提交任务。
timeout:指当遍历iterables时延迟了timeout秒后仍然未获取到内容时抛
concurrent.futures.TimeoutError异常, timeout可为int或float类型。 若timeout未指定或为None,则不限制等待时间。
chunksize:仅当使用进程池ProcessPoolExecutor时有效,该参数必须是正整数,
用来指定将iterables分割成几份提交任务。默认值是
1表示不分割,当任务数量很多时分割提交任务可以显著提高性能。该参数是3.5版以后新增的。
----------------------------------------------------------------------------------------
shutdown方法,shutdown(wait=True, *, cancel_futures=False):
功能:
用来关闭进程/线程池。
如果使用with语句,你就可以避免显式调用这个方法,它会在全部任务执行完毕或异常退出时自动调用
shutdown(wait=True)。
参数:
wait:用来指定何时关闭进程/线程池。若wait为True则在所有任务执行完毕后才关闭进程/线程池,默
认值是True;若wait为False则不再接收后续任务,待正在执行的任务完成后立即关闭进程/线程池。
cancel_futures:用来指定是否取消尚未开始的任务。True表示取消;False表示不取消,默认值为
False。该参数是3.9版以后新增的。
线程池,使用submit方式提交任务:
from threading import current_thread
import time, random
from concurrent.futures import ThreadPoolExecutor # 导入线程池类
def func(n):
print(current_thread().ident, f'任务“{n}”开始运行!')
time.sleep(random.randint(1, 3))
print(current_thread().ident, f'任务“{n}”运行完毕。')
if __name__ == '__main__': # win平台下必须要加,linux和mac平台下可以不加。
pool = ThreadPoolExecutor(3) # 创建包含3条线程的线程池
for i in range(9):
pool.submit(func, i) # 提交任务
进程池,使用submit方式提交任务:
from multiprocessing import current_process
import time, random
from concurrent.futures import ProcessPoolExecutor # 导入进程池类
def func(n):
print(current_process().ident, f'任务“{n}”开始运行!')
time.sleep(random.randint(1, 3))
print(current_process().ident, f'任务“{n}”运行完毕。')
if __name__ == '__main__': # win平台下必须要加,linux和mac平台下可以不加。
pool = ProcessPoolExecutor(3) # 创建包含3条进程的进程池
for i in range(9):
pool.submit(func, i) # 提交任务
回调方法(进程和线程方法一样)
from concurrent.futures import ThreadPoolExecutor
import time, random
def pow2(n):
time.sleep(random.uniform(1, 4)) # 随机休息1至4秒,方便看清多线程运行效果。
return n, n ** 2
def print_pow2(fut): # 打印pow2处理结果的回调函数
print(f'{fut.result()[0]}的运算结果是{fut.result()[1]}')
pool = ThreadPoolExecutor(3) # 开启3线程执行任务
for i in range(7): #
pool.submit(pow2, i).add_done_callback(print_pow2) # 线程池执行后返回future实例
'''
ps:使用add_done_callback方法绑定回调函数print_pow2,在pow2返回结果后print_pow2立即被调
用,print_pow2只能接收一个参数即future实例,使用该实例的result方法可以得到pow2的返回结果。
使用回调方法的好处是多线程并发执行时任何一条线程运行完毕后都可立即用回调函数对它的返回值进行处
理。如果不用回调方法那么只能等到全部任务运行结束后才能对返回值进行处理。
'''
协程
协程是协调个部分代码达到资源最大利用,这才是真正的协程,在协程中要有任务的安排调整。
协程就是这样发生在一个可能发生长时间阻塞的地方,我们不是让CPU做无用的等待,而是让CPU在等待的
时间干点其他有用的事情,我们手动进行任务切换的过程就是协程。
gevent的介绍
greenlet已经实现了协程,但是这个还要人工切换,这里介绍一个比greenlet更强大而且能够自动切换
任务的第三方库,那就是gevent。
gevent内部封装的greenlet,其原理是当一个greenlet遇到IO(指的是input output 输入输出,比
如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当
的时候切换回来继续执行。
由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有
greenlet在运行,而不是等待IO。
安装gevent
pip3 install gevent
给程序打补丁
from gevent import monkey
# 打补丁,让gevent框架识别耗时操作,比如:time.sleep,网络请求延时
monkey.patch_all()
实现
from gevent import monkey;monkey.patch_all() # 固定编写 用于检测所有的IO操作
from gevent import spawn
import time
def play(name):
print('%s play 1' % name)
time.sleep(5)
print('%s play 2' % name)
def eat(name):
print('%s eat 1' % name)
time.sleep(3)
print('%s eat 2' % name)
start_time = time.time()
g1 = spawn(play, 'jason')
g2 = spawn(eat, 'jason')
g1.join() # 等待检测任务执行完毕
g2.join() # 等待检测任务执行完毕
print('总耗时:', time.time() - start_time) # 正常串行肯定是8s+
GIL与普通互斥锁区别,死锁现象,信号量,event事件,进程池与线程池,协程的更多相关文章
- python 之 并发编程(守护线程与守护进程的区别、线程互斥锁、死锁现象与递归锁、信号量、GIL全局解释器锁)
9.94 守护线程与守护进程的区别 1.对主进程来说,运行完毕指的是主进程代码运行完毕2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕详细解释:1.主 ...
- GIL全局解释器锁、死锁现象、python多线程的用处、进程池与线程池理论
昨日内容回顾 僵尸进程与孤儿进程 # 僵尸进程: 所有的进程在运行结束之后并不会立刻销毁(父进程需要获取该进程的资源) # 孤儿进程: 子进程正常运行 但是产生该子进程的父进程意外死亡 # 守护进程: ...
- 并发编程: GIL锁、GIL与互斥锁区别、进程池与线程池的区别
一.GIL 二.关于GIL性能的讨论 三.计算密集测试 四.IO密集测试 五.GIL与互斥锁 六.TCP客户端 七.进程池 八.进程什么时候算是空闲 九.线程池 一.GIL GIL Global In ...
- 同步锁,死锁现象与递归锁,信息量Semaphore.....(Day36)
一.同步锁 三个需要注意的点: #1.线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行 ...
- python并发编程-多线程实现服务端并发-GIL全局解释器锁-验证python多线程是否有用-死锁-递归锁-信号量-Event事件-线程结合队列-03
目录 结合多线程实现服务端并发(不用socketserver模块) 服务端代码 客户端代码 CIL全局解释器锁****** 可能被问到的两个判断 与普通互斥锁的区别 验证python的多线程是否有用需 ...
- TCP协议下的服务端并发,GIL全局解释器锁,死锁,信号量,event事件,线程q
TCP协议下的服务端并发,GIL全局解释器锁,死锁,信号量,event事件,线程q 一.TCP协议下的服务端并发 ''' 将不同的功能尽量拆分成不同的函数,拆分出来的功能可以被多个地方使用 TCP服务 ...
- GIL全局解释器锁-死锁与递归锁-信号量-event事件
一.全局解释器锁GIL: 官方的解释:掌握概念为主 """ In CPython, the global interpreter lock, or GIL, is a m ...
- python同步、互斥锁、死锁
目录 同步 同步的概念 解决线程同时修改全局变量的方式 互斥锁 使用互斥锁完成2个线程对同一个全局变量各加9999999 次的操作 上锁解锁过程 总结 死锁 避免死锁 同步 同步的概念 同步就是协同步 ...
- GIL解释器锁 & 进程池与线程池
今日内容 GIL 全局解释器锁(重要理论) 验证 GIL 的存在及功能 验证 python 多线程是否有用 死锁现象 进程池与线程池(使用频率高) IO模型 详细参考: https://www.bil ...
随机推荐
- 你是如何调用 wait()方法的?使用 if 块还是循环?为什么?
wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条 件可能还没有满足,所以在处理前,循环检测条件是否满足会更好.下面是一段 标准的使用 wait 和 notify 方法 ...
- MyBatis Plus 2.3 个人笔记-01-代码生成器
sb_mybatis_puls2.3 <?xml version="1.0" encoding="UTF-8"?> <project xmln ...
- 运筹学之"简单平均预测法"和"加权滑动平均预测法"和"确定平滑系数"
1.简单滑动平均预测法就是将所有的售价加起来除以总数 665/5=133 2.加权滑动平均预测法:需要将售价分别乘以权之和,并除以权之和 1771/13≈136.23 二.某木材公司销售房架构件,其中 ...
- 定时任务__@Xxl-JOB的使用
概述xxl-job框架 首先我们要知道什么是XXL-JOB? 官方简介:XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速.学习简单.轻量级.易扩展.现已开放源代码并接入多家公司 ...
- Numpy的数学统计函数
Numpy的数学统计函数 本节内容: 1.Numpy有哪些数学统计函数: 函数名 说明 np.sum 所有元素的和 np.prod 所有元素的乘积 np.cumsum 元素的累积加和 np.cumpr ...
- 罗振宇2022"时间的朋友"跨年演讲
罗振宇2022"时间的朋友"跨年演讲 行就行,不行我再想想办法. 原来,还能这么干! 堆资源不是解决问题的唯一道路,还是那句话:"处于困境中的人往往只关注自己的问题.而解 ...
- dll反编译(修改引用文件、修改代码)再生成dll
问题描述 我们在日常开发中经常会遇到,想要对dll文件做修改的操作,但苦于没有源代码,只能想想其他办法 解决问题 办法就是通过几个工具来反编译.正向编译.修改属性 反编译.正编译 参考https:// ...
- react、react-router、redux 也许是最佳小实践1
小前言 这是一个小小的有关react的小例子,希望通过一个小例子,可以让新手更好的了解到react.react-router4.0.redux的集中使用方法. 这是基于create-react-app ...
- Unity中让Update中的方法执行一次
Unity中让Update中的方法执行一次 Unity中,很多时候,代码需要放在Update中时刻监测状态,一旦状态符合,又只需要代码执行一次:其实可以通过设置控制量的方式,让代码只执行一次:方法:设 ...
- 【weex开发】weex官方源码
公司目前使用版本:weex_sdk:0.10.0 介绍地址:https://bintray.com/alibabaweex/maven/weex_sdk/0.18.0 weex最新版本:weex_sd ...