并发编程--一堆锁,GIL,同步异步,Event事件
一堆锁
死锁现象(*****)
死锁指的是,某个资源被占用之后,一直得不到释放,导致其他需要这个资源的线程进入阻塞状态
产生死锁的情况
对同一把互斥锁,进行了多次加锁
一个共享资源,在访问时必须具备多把锁,但是这些锁被不同的线程或进程所持有,这样会导致相互等待对方释放,从而程序卡死
解决方案:
- 按照相同的顺序进行抢锁
- 给抢锁加上超时,如果超时就放弃抢锁
递归锁 RLock (了解)
与普通的互斥锁相同点在于,在多线程之间有互斥的效果但是同一个线程中,递归锁可以多次加锁,执行多次acquire
同一个线程必须保证 加锁的次数和解锁的次数相同 其它线程才能够抢到这把锁
RLock只是解决了代码逻辑上的错误导致的死锁,并不能解决多个锁造成的死锁问题
# 同一把RLock 多次acquire
#l1 = RLock()
#l2 = l1
# 不同的RLock 依然会锁死
#l1 = RLock()
#l2 = RLock()
def task():
l1.acquire()
print(threading.current_thread().name,"拿到了筷子")
time.sleep(0.1)
l2.acquire()
print(threading.current_thread().name, "拿到了盘子")
print("吃饭")
l1.release()
l2.release()
def task2():
l2.acquire()
print(threading.current_thread().name, "拿到了盘子")
l1.acquire()
print(threading.current_thread().name,"拿到了筷子")
print("吃饭")
l2.release()
l1.release()
t1 = Thread(target=task)
t1.start()
t2 = Thread(target=task2)
t2.start()
信号量 (了解)
可以用来限制同时并发执行公共代码的线程数量
如果限制数量为1,则和普通的互斥锁一样
from threading import Thread, Semaphore, currentThread
import time
s = Semaphore(3) # 一次只能有3个线程占用
def task():
s.acquire()
time.sleep(1)
print(currentThread().name)
s.release()
for i in range(10):
Thread(target=task).start()
注意:信号量不是用来解决安全问题的 而是用于限制最大的并发量
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中,全局解释器锁(global interpreter lock, GIL)是一个互斥体,它防止多个本机线程同时执行Python字节码。这个锁是必要的,主要是因为CPython的内存管理不是线程安全的。(然而,自从GIL存在以来,其他特性已经逐渐依赖于它强制执行的保证。)
'''
GIL本质是一把互斥锁,但GIL锁住的是解释器级别的数据
自定义锁,锁的是解释器以外的共享资源,例如:硬盘上的文件 控制台,对于这种不属于解释器的数据资源就应该自己加锁处理
为什么需要GIL锁
python程序本身只是一串字符,只有通过python解释器运行之后才具有意义,但是在一个python程序中python解释器只有一个,所有代码都是由它来执行的,当多个线程都需要执行代码时,就会产生线程安全问题。
如果我们不开线程,是否就没有问题了?
Cpython解释器与GC的问题
GC线程又叫垃圾回收机制
在python中,解释器会自动帮我们进行垃圾回收,这也是需要开启一个线程来进行的,也就是说,当我们没有开启子线程的时候,内部还是有多个线程在执行。所以这就会带来线程安全问题
例如:
线程a要定义一个变量 b,首先申请一块空内存,然后把数据装进去,此时变量b的引用计数为0,最后才会将引用计数加一
但是,如果在刚把数据装进去的时候,CPU切换到GC线程,GC线程就会把变量b当作垃圾进行回收
GIL锁带来的问题
GIL锁本质还是一把互斥锁,所以就会降低程序运行效率
没有解决办法,只有尽可能的避免GIL带来的影响
使用多线程实现并行,更好的利用多核CPU
对任务进行区分
计算密集型
基本没有IO操作,大部分时间都是在进行计算,例如人脸识别,图像处理
由于线程不能并行,应该使用多进程,将任务分给不同的CPU进行
IO密集型
计算任务非常少,大部分时间都在等待IO操作
由于网络IO速度对比CPU处理速度非常慢,多线程并不会造成太大的影响,另外如有大量客户端连接服务,进程根本开不起来,只能用多线程
多线程与多进程性能对比
加锁主要就是为了解决线程安全问题,但这也导致了Cpython中,多线程只能并发而不能并行
python的优势:
python是一门语言,GIL是Cpython解释器的问题 ,Jpython,pypy 并没有
如果是单核CPU,GIL不会造成任何影响
由于目前大多数程序都是基于网络的,网络速度对比CPU是非常慢的,导致即使多核CPU也无法提高效率
对于IO密集型任务,不会有太大的影响
如果没有这把锁,我们程序猿将必须自己来解决安全问题
性能测试 :
计算密集型任务
from multiprocessing import Process
from threading import Thread
import time
# # 计算密集型任务 def task():
for i in range(100000000):
1+1 if __name__ == '__main__':
start_time = time.time()
ps = []
for i in range(5):
# p = Process(target=task) # 4.420854806900024
p = Thread(target=task) # 20.043513298034668
p.start()
ps.append(p) for i in ps:i.join()
print("共耗时:",time.time()-start_time)
IO密集型
from multiprocessing import Process
from threading import Thread
import time
# IO密集型任务 def task():
for i in range(100):
with open(r"1.死锁现象.py", 'r', encoding="utf-8") as fr:
fr.read() if __name__ == '__main__':
start_time = time.time() ps = []
for i in range(10):
# p = Process(target=task) # 1.1040928363800049
p = Thread(target=task) # 0.09919905662536621
p.start()
ps.append(p) for i in ps:i.join()
print("共耗时:",time.time()-start_time)
进程池与线程池
池是一个容器,可以用来装一些东西,但池也是有容量限制的,不可能无限制的装东西
进程池和线程池就是用来装进程和线程的容器
进程池和线程池的好处:
- 可以避免频繁的创建和销毁(进程/线程)来的资源开销
- 可以限制同时存在的线程数量,以保证服务器不会应为资源不足而导致崩溃
- 帮我们管理了线程的生命周期
- 管理了任务的分配
进程池与线程池的使用
线程池
from concurrent.futures import ThreadPoolExecutor
from threading import enumerate,currentThread # 创建一个线程池 指定最多可以容纳两个线程
pool = ThreadPoolExecutor(2) def task():
print(currentThread().name) # 提交任务到池子中
pool.submit(task)
pool.submit(task) print(enumerate())
进程池
import os
import time
from concurrent.futures import ProcessPoolExecutor # 创建一个进程池, 指定最多可以容纳两个线程
def task():
time.sleep(1)
print(os.getpid()) if __name__ == '__main__':
pool = ProcessPoolExecutor(2)
pool.submit(task)
pool.submit(task)
pool.submit(task)
注意:如果进程不结束,池子里面的进程或线程也是一直存活的
同步异步(*****)
**异步同步指的是提交任务的方式 **
同步:是指提交任务后,必须需要等待任务完成之后才能进行下一个任务
异步:是指提交任务后,不需要等待任务完成,继续进行下一个任务
异步效率高于同步,但异步任务将导致一个问题,就是任务的发起方不知道任务何时处理完毕
解决办法:
轮询:每隔一段时间询问一次
结果:效率低,无法及时获取结果
异步回调:让任务的执行方主动通知
结果:可以及时拿到任务的结果
案例:
from threading import Thread
# 具体的任务
def task(callback):
print('run')
for i in range(100000000):
1 + 1
callback(True)
# 回调函数,参数为任务的结果
def finished(res):
if res:
print('任务完成')
else:
print('任务失败')
print('start...')
t = Thread(target=task,args=(finished,))
t.start()
print('over')
线程池中回调的使用
from concurrent.futures import ThreadPoolExecutor
def task(num):
time.sleep(1)
print(num)
return "hello python"
def callback(obj):
print(obj.result())
pool = ThreadPoolExecutor()
res = pool.submit(task,123)
res.add_done_callback(callback)
print("over")
Event事件
Event用于线程间状态同步,
执行状态:指的是程序运行到哪一步,此时的状态
执行结果:程序运行完了得到的结果
如果我们要拿到执行结果,可以采用异步回调
Event事件的本质就是一个标志,True or False
Evevt里面包含了一个wait函数,可以阻塞当前线程,直到状态由False变为True
from threading import Thread, Event
import time
e = Event()
# is_bool = False
def start_server():
# global is_bool
print('starting server.....')
time.sleep(3)
print('server started')
# is_bool = True
e.set()
def connect_server():
e.wait()
if e.is_set():
print('连接服务器成功')
# while True:
# if is_bool:
# print('连接服务器成功')
# break
# else:
# print('连接服务器失败')
#
# time.sleep(0.1)
t1 = Thread(target=start_server)
t2 = Thread(target=connect_server)
t1.start()
t2.start()
并发编程--一堆锁,GIL,同步异步,Event事件的更多相关文章
- Python之路(第三十六篇)并发编程:进程、同步异步、阻塞非阻塞
一.理论基础 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一.操作系统的其他所有内容都是围绕进程的概念展开的. 即使可以利用的cpu只有一个(早期的 ...
- 并发编程(五)——GIL全局解释器锁、死锁现象与递归锁、信号量、Event事件、线程queue
GIL.死锁现象与递归锁.信号量.Event事件.线程queue 一.GIL全局解释器锁 1.什么是全局解释器锁 GIL本质就是一把互斥锁,相当于执行权限,每个进程内都会存在一把GIL,同一进程内的多 ...
- Java并发编程:锁的释放
Java并发编程:锁的释放 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: #839496;} Ja ...
- Java并发编程:线程的同步
Java并发编程:线程的同步 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: #839496;} J ...
- Java并发编程的4个同步辅助类
Java并发编程的4个同步辅助类(CountDownLatch.CyclicBarrier.Semphore.Phaser) @https://www.cnblogs.com/lizhangyong/ ...
- 并发编程(五)--GIL、死锁现象与递归锁、信号量、Event事件、线程queue
一.GIL全局解释器锁 1.什么是全局解释器锁 GIL本质就是一把互斥锁,相当于执行权限,每个进程内都会存在一把GIL,同一进程内的多个线程,必须抢到GIL之后才能使用Cpython解释器来执行自己的 ...
- Day12- Python基础12 线程、GIL、Lock锁、RLock锁、Semaphore锁、同步条件event
http://www.cnblogs.com/yuanchenqi/articles/6248025.html 博客地址 本节内容: 1:进程和线程的说明 2:线程的两种调用方式 3:threadi ...
- 1.gil全局解释器锁, 2. 死锁与递归锁 3. 信号量 4. Event事件 5. 线程queue
gil本质就是一把互斥锁,相当于执行权限,每个进程都会存在一把gil,同一进程内的多个线程必须抢到gil 之后才能使用cpython解释器来执行自己的代码,同一进程下的多线程不能并行,但可以实现并发 ...
- python并发编程之IO模型 同步 异步 阻塞 非阻塞
IO浅谈 首先 我们在谈及IO模型的时候,就必须要引入一个“操作系统”级别的调度者-系统内核(kernel),而阻塞非阻塞是跟进程/线程严密相关的,而进程/线程又是依赖于操作系统存在的,所以自然不能脱 ...
随机推荐
- 201871010134-周英杰《面向对象程序设计(Java)》第四周学习总结
201871010134-周英杰<面向对象程序设计(Java)>第四周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这 ...
- java任务调度之Timer定时器
定时器相信大家都不陌生,平时使用定时器就像使用闹钟一样,我们可以在固定的时间做某件事,也可以在固定的时间段重复做某件事,今天就来分析一下java中自带的定时任务器Timer. 一.Timer基本使用 ...
- FastDFS 分布式文件系统(部署和运维)
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/KamRoseLee/article/det ...
- 接口测试中,数据驱动时,参数各类型,空或None的处理
天天说接口测试,天天说数据驱动,但网上的各种教程太烂,遇到实际情况就傻眼了. 来来来,我们看一个例子 假设,有下面这样一个接口,获取用户信息,可以带的参数如下: 用户名(uname) str(),非必 ...
- .Net反射-基础2-BindingFlags参数
BindingFlags参数用于指定反射查找的范围在调用下列方法时会用到BindingFlags参数 // 调用方法. InvokeMethod // 创建实例. CreateInstance // ...
- Vue 生成PDF并下载
实现原理 该功能原理是将页面转化伟canvas在把canvas转化为base64数据 最后将数据通过pdf.js生成下载,故需要和html2canvas一起使用 友情提醒这个pdf下载不能在app里直 ...
- Console工程下如何不显示控制台黑窗口只显示Windows窗口
原文地址https://www.cnblogs.com/smiler/p/4575052.html OpenGL,绘制图形的时候,如果不进行设置,运行的时候会先出现黑窗口再出现Windows窗口. 其 ...
- Windows平台搭建NodeJs开发环境以及HelloWorld展示—图解
Windows 7系统下搭建NodeJs开发环境(NodeJs+WebStrom)以及Hello World!展示,大体思路如下:第一步:安装NodeJs运行环境.第二步:安装WebStrom开发工具 ...
- LOJ6115 汇合 树上分块
本题空间很小,那些O(nlogn)的树上lca算法在这里不顶用了,可以考虑树分块. 本题的树分块是基于深度的,即按深度每\(\sqrt n\)分一块,然后一块一块往上跳,一直跳到lca处. 对于这题, ...
- word2vec学习总结
目录 1.简介 2.从统计语言模型开始 2.1序列概率模型 2.2 N元统计模型 3.深度序列模型 3.1神经概率模型 3.2 one-hot向量表示法 3.3 word2vec 3.4word2ve ...