今日内容:
1.异步同步 和 阻塞非阻塞 概念 ******
2.异步回调 ******
为什么需要回调
子进程帮助主进程完成任务 处理任务的结果应该是、交给准进程
其他方式也可以将数据交还给主进程
1.shutdown 主进程会等所有任务完成
2.result函数 会阻塞直到完成任务
都会阻塞 导致效率降低 所以使用回调
注意:
回调函数什么时候被执行? 子进程完成时
谁在执行回调函数? 主进程
线程的异步回调
使用方式都相同 唯一不同的是执行回调函数 是子进程在执行
3.线程列队 ****
列队
堆栈
优先级列队
4.协程
协程的目的是在单线程下实现并发
为什么出现协程?因为cpython由于GIL 导致同一时间只有一个线程再跑
意味着 如果你的线程序计算密集 多线程效率也不会提升
如果是io密集型 有没有必要再单线程下实现并发
没有 我还开启多线程来处理io 子线程遇到io cpu切走 但是请问 你能保证一定切换到主线程吗? 不能保证
有 如果可以 我在遇到io的时候转而去做计算 这样一来可以保证cpu一直在处理你的程序 当然时间太长也会切换
总结:单线程下实现并发 将io阻塞时间用于执行计算 可以提高效率 原理:一直使用cpu直到超时
怎么实现单线程并发?
并发 指的是 看起来像是同时运行 实际上是在任务之间来回切换 同时需要保存执行的状态
任务一堆代码 可以用函数装起来
1.如何让两个函数切换执行
yield可以保存函数的执行状态
通过生成器还有实现为并发
并发不一定提升效率 反而会降低效率 当任务全是计算时
2.如何知道发生了io?从而切换执行
目前咱们实现不了
第三方模块 greenlet 可以实现并发 但是不能检测io
第三方模块gevent 封装greenlet 可以实现单线程并发 并且可以检测io操作 自动切换
同步异步:
线程的三种状态:
1.就绪
2.运行
3.阻塞
阻塞 遇到了io操作 代码卡住 无法执行下一行 CPU会切换的其他任务
非阻塞 雨阻塞相反 代码正在执行(运行状态)或者处于就绪状态
阻塞和非阻塞描述的是运行的状态
同步:提交任务必须等待任务完成 才能执行下一行
异步:提交任务不需要等待任务完成立即执行下一行
指的是一种提交任务的方式
def task(): for i in range(1000000): i += 1000 print("11111") print("start") task() # 同步提交方式 print("end") from threading import Thread print("start1") Thread(target=task).start() # 异步提交 print("end1")
2.利用回调完成生产者消费:
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor from threading import current_thread import os pool = ThreadPoolExecutor() # 爬虫 1.从网络某个地址获取一个HTML文件 import requests # 该模块用于网络(HTTP)请求 # 生产数据 def get_data_task(url): print(os.getpid(),"正在生产数据!") # print(current_thread(),"正在生产数据!") response = requests.get(url) text = response.content.decode("utf-8") print(text) return text # 处理数据 def parser_data(f): print(os.getpid(),"处理数据") # print(current_thread(), "处理数据") print("正在解析: 长度%s" % len(f.result())) urls = [ "http://www.baidu.com", "http://www.baidu.com", "http://www.baidu.com", "http://www.baidu.com" ] if __name__ == '__main__': for url in urls: f = pool.submit(get_data_task,url) f.add_done_callback(parser_data) # 回调函数是主进程在执行 # 因为子进程是负责获取数据的 然而数据怎么处理 子进程并不知道 应该把数据还给主进程 print("over")
3.线程队列:
import queue # 普通队列 先进先出 q = queue.Queue() q.put("a") q.put("b") print(q.get()) print(q.get()) # 堆栈队列 先进后出 后进先出 函数调用就是进栈 函数结束就出栈 递归造成栈溢出 q2 = queue.LifoQueue() q2.put("a") q2.put("b") print(q2.get()) # 优先级队列 q3 = queue.PriorityQueue() # 数值越小优先级越高 优先级相同时 比较大小 小的先取 q3.put((-100,"c")) q3.put((1,"a")) q3.put((100,"b")) print(q3.get())
4.协程实现:
mport time def task(): while True: print("task1") time.sleep(4) yield 1 def task2(): g = task() while True: try: print("task2") next(g) except Exception: print("任务完成") break task2()
5.greenlet使用:
import greenlet import time def task1(): print("task1 1") time.sleep(2) g2.switch() print("task1 2") g2.switch() def task2(): print("task2 1") g1.switch() print("task2 2") g1 = greenlet.greenlet(task1) g2 = greenlet.greenlet(task2) g1.switch()
1.实例化greenlet得到一对象 传入要执行的任务
至少需要两个任务
2.先让某一个任务执行起来 使用对象调用switch
3.在任务的执行过程中 手动调用switch来切换
6.gevent使用:
from gevent import monkey monkey.patch_all() import gevent import time def eat(): print('eat food 1') time.sleep(2) # gevent.sleep(1) print('eat food 2') def play(): print('play 1') time.sleep(1) # gevent.sleep(1) print('play 2') g1=gevent.spawn(eat) g2=gevent.spawn(play) # g1.join() # g2.join() gevent.joinall([g1,g2]) print('主')
1.spawn函数传入你的任务
2.调用join 去开启任务
3.检测io操作需要打开monkey补丁 就是一个函数 在程序最开始的地方调用它