复习
1.GIL锁
2.如何避免GIL锁给程序带来的效率影响
3.与自定义锁的区别
4. 线程池进程池
5 同步 异步
6.异步回调
1.GIL锁
? 全局解释器锁, 用来锁住解释器的互斥锁
? 为啥加: CPython 中内存管理是非线程安全的, GIL是为了 保护解释器的数据不被并发修改
? 加锁后的问题:导致多个线程无法并行执行, 降低了效率
? 当然 是可以并发的
?
2.如何避免GIL锁给程序带来的效率影响
? 什么时候会影响效率
? 如果是计算密集型任务,开多线程不能提高效率 ,还不如一条线程来得快
? 在Cpython应该使用多进程来处理计算密集型任务 (在多 核下才能真正的提高效率 )
? 例如:图像处理,语音识别 ,语义识别(高端多了)
? 1920*1080
? 如果是IO密集型任务,GIL基本上没什么影响 ,
? 应该是使用多线程,以为大量时间都消耗在IO操作上了, 开启多进程反而会浪费资源
? 例如:聊天 , 网页等
3.与自定义锁的区别
? 都是互斥锁,但是锁的资源不同,
? GIL 锁的是解释器中资源 例如:引用计数,运行状态
? 自定义锁,锁的是自己共享的资源
?
4. 线程池进程池
? 池是容器,
? 用于存储线程/进程的容器
? 还帮我管理了,线程的创建于销毁,以及任务的分配
? 1.创建池子
? 2.submit 提交任务
? 3.shutdown 可以用于等待所有任务完成后销毁池
5 同步 异步
? 同步是说 任务发起后必须在原地等待任务执行结束,才能拿到结果
? 异步 任务发起后不需要等待,可以继续执行其他操作
?
? 异步任务的问题: 任务发起方不知道何时任务结束. 如果需要获取结果 就不知道该什么时候获取结果
? 解决方案:
? 给任务绑定一个回调函数,会在任务执行完毕后 自动执行
? 1.结果可以被及时处理
? 2.任务发起方不需要的等待任务结束,提高了效率
?
线程池和进程池异步回调的区别
? 线程池中回调函数 .是在子线程中被执行
? 进程池中回调函数 .是在父进程中被执行, 进程间不能直接通讯,所以回调函数,已经完成了IPC 把数据还给了父进程
#### 异步任务通常都会绑定一个回调函数
#### 异步回调的原理:
在发起任务时,传入一个函数作为回调函数 ,在任务完成执行该函数并将任务结果作为参数传入
```python
from threading import Thread
import time
# res = None
def call_back(res):
print("任务结果拿到了:%s" % res)
def parser(res):
print("任务结果拿到了:%s" % res)
def task(callback):
# global res
print("run")
time.sleep(1)
# # return 100
res = 100 # 表示任务结果
callback(res) # 执行回调函数 并传入任务结果
t = Thread(target=task,args=(parser,))
t.start()
# t.join()
# print(res)
print("over")
```
如果是进程回调 还需要考虑数据通讯的问题,而且最难的是 需要让父进程在拿到结果时立即触发回调函数的执行 父进程不应该卡住
今日内容
1.线程一堆队列 了解
2.事件Event
3.协程
4.断点续传
1.线程一堆队列 了解
# 与进程中的Joinablequeue 使用方式一模一样 但是 不具备IPC
# last in first out 后进先出 先进 后出 模拟堆栈 ===========================================================# LifoQueue # 除顺序以外别的都一样# lq = LifoQueue()
# 具备优先级的队列# PriorityQueue ================================================================# 可以存储一个可以比较大小的对象 比较越小的优先级越高 自定义对象 不能使用比较运算符 所以不能存储
2.事件Event
? 事件,表示发生了某件事情,我们可以去关注某个事件然后采取一些行动
? 本质上事件是用来线程间通讯的 ,用于状态同步
案例:
? 有两条线程,一个用于启动服务器 一个用于客户端链接到服务器
? 条件是 服务器启动成功客户端才能链接成功!
```python
boot_event = Event()
# boot_event.clear() 回复事件的状态为False
# boot_event.is_set() 返回事件的状态
# boot_event.wait()等待事件发生 ,就是等待事件被设置为True
# boot_event.set() 设置事件为True
def boot_server():
print("正在启动服务器......")
time.sleep(3)
print("服务器启动成功!")
boot_event.set() # 标记事件已经发生了
def connect_server():
boot_event.wait() # 等待事件发生
print("链接服务器成功!")
t1 = Thread(target=boot_server)
t1.start()
t2 = Thread(target=connect_server)
t2.start()
```
# 爬虫 中使用event的一个案例
总共有一百个是要看 如果一次全开 内存扛不住 所以思路 每次开5个
用另一个线程来监控窗口数量 如果小于则继续开启窗口
这是打开窗口的线程 要执行的任务
for i in range(100):
? event.wait()
? 打开一个窗口
起另一个线程来监控视频的窗口数量 ,当数量小于5的时候 再开新窗口
while True:
? 判断视频的窗口数量是否为5:
? True: event.clear()
? False: event.set()
3.协程 `*****`
? 协程的目的就是要在单线程中实现并发
?
? 并发:
? 多个任务看起来是同时运行 本质是切换+保存状态
?
? 生成器,中 yield 就可以保存当前函数的运行状态
```python
# 使用生成器来实现 单线 并发多个任务
import time
# def func1():
# a = 1
# for i in range(10000000):
# a += 1
# # print("a run")
# yield
#
# def func2():
# res = func1()
# a = 1
# for i in range(10000000):
# a += 1
# # print("b run")
# next(res)
#
# st = time.time()
# func2()
# print(time.time() - st)
def func1():
a = 1
for i in range(10000000):
a += 1
def func2():
a = 1
for i in range(10000000):
a += 1
st = time.time()
func1()
func2()
print(time.time() - st)
```
经过测试 单线程并发并不能提高性能 , 对于计算密集型任务而言
对于IO操作而言 必须具备能够检测io操作 并自动切换其他区任务 ,这才提高效率
# greenlet
? 直接使用yield 可以并发 但是代码结构太乱,所以greenlet进行了封装
? greenlet需要手动切换,而且不能 检测IO
```python
import greenlet
import time
def task1():
print("task1 run")
g2.switch()
print("task1 over")
g2.switch()
def task2():
print("task2 run")
g1.switch()
time.sleep(2)
print("task2 over")
g1 = greenlet.greenlet(task1)
g2 = greenlet.greenlet(task2)
g1.switch()
# g2.switch()
print("主 over")
```
# gevent
# 什么是协程
gevent 是协程
协程 翻译为轻量级线程 ,也称之为微线程,
是应用程序级别的任务调度方式
# 协程对比线程
应用程序级别调度:我可以在检测到IO操作时,立马切换到我的其他任务来执行
? 如果有足够的任务来执行,就可以把CPU的时间片充分利用起来
操作系统级别调度:遇到IO操作系统就会拿走CPU, 下一次分给哪个进程就不得而知了
# 如何提高效率
咱们使用Cpython 到底如何提高效率
在Cpython中有GIL锁 导致多线程不能并行执行丧失了 多核优势,
即使开启了多线程也只能并发, 这时候完全 可以使用协程来实现并发
优点:不会占用更多无用的资源
缺点:如果是计算任务 使用协程反而降低效率
## 终极杀招 对IO密集任务
? 在系统可承受范围内 开启多进程
? 在每个进程下开多个协程任务
# 使用场景:
? Cpython的IO密集型任务
? 对于本来就可以利用多核并行的场景下,没别要开协程
# 猴子补丁
本质就是把原本阻塞的代码 悄悄换成非阻塞代码
例如 Queue.get(block=false)
? 当执行get而取不到值时 会抛出异常 只需捕获异常 然后在发生时切换到其他任务
? 就可以实现遇到IO 切换任务
# 大前提 我们要解决的是IO密集型任务的问题
什么是协程:
? 协程是轻量级线程,也称之为为微线程, 是可以由应用程序自己来调度的任务方式
? 协程本质:单线程实现并发
为什么使用协程:
? 场景:
? 在Cpython 多线程无法并行 执行 ,在IO密集任务中 并且并发量较大,无法开启更多的线程时,
? 造成后续的任务无法处理,即使前面都在等待IO
? 协程: 就可以使用单线程 实现并发,当某一个任务处于IO阻塞时,可以切换到其他的任务来执行
? 可以充分利用CPU时间片,如果任务量足够,可以占用CPU直到超时
最终的解决方案
多进程 +单线程 + 协程
如果还是扛不住:
? 1.集群 所有服务器干的活都一样
? 2.分布式 每个服务器就干某个活
线程
gevent是对greenlet的封装
可以帮我们自动切换任务,并且在打上补丁以后,可以检测io操作
这样一来就可以 在遇到IO时自动切换其他任务从而提高效率
4.断点续传
断点续传 任务进行过程中,由于某些原因导致任务没有完成在重新启动程序之后 可以接着上次断掉的位置继续传输 =========== ================ 服务器=========== 50 ================ 1.客户端 需下载某个文件 输入一个文件名称 判断该文件是否已经下载 判断文件是否传输完毕 一个任务存在的状态 1.新任务 本地没有 文件不存在 并且任务记录中也不存在 2.任务未完成 本地有 但是不完整 文件已经存在 但是任务记录标记为未完成 3.任务已完成 本地有完整的数据 文件已经存在 任务标记为已完成 4.任务已经完成 但是数据被删除 文件不存在 任务标记为完成 任务记录? 新任务 需要添加到记录中 未完成 下载完成 修改记录为完成 需要哪些数据? 任务名称 文件名称 状态 未完成 已完成 {"name":False} 字典存储任务 需要永久存储 使用方法将测试文件放在服务器的 serverFiles中
?
?
?
?
?
?
?
原文地址:https://www.cnblogs.com/llx--20190411/p/10986609.html