1. 阻塞,非阻塞,同步,异步
进程运行的三个状态: 运行,就绪,阻塞.
从执行的角度:
阻塞: 进程运行时,遇到IO了,进程挂起,CPU被切走.
非阻塞: 进程没有遇到IO 当进程遇到IO,但我通过某种手段,让CPU强行运行我的进程
提交任务的角度:
同步: 提交一个任务,自任务开始运行直到此任务结束(可能有IO),返回一个返回值之后,我在提交下一个任务.
异步: 一次提交多个任务,然后我就直接执行下一行代码.
返回的结果,应该如何回收?
eg: 给三个老师发布任务:
同步: 先告知第一个老师完成写书的任务,然后我在原地等待,等他两天之后完成了,告知我完事了,我才发布下一个任务........
异步: 直接将三个任务告知三个老师,我就忙我的,知道三个老师完成之后,告知我.
2. 同步调用,异步调用
- 同步调用:
# 同步调用 from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor import time import random import os def task(i): print(f"{os.getpid()} 开始任务") time.sleep(random.randint(1,3)) print(f"{os.getpid()} 任务结束") return i if __name__ == '__main__': # 同步调用 pool = ProcessPoolExecutor() for i in range(10): obj = pool.submit(task,i) # obj是一个动态对象,返回的是当前的对象的状态,有可能运行中,可能(就绪阻塞),还可能是结束了. # obj.result() 必须等到这个任务完成后,返回了结果之后,再能执行下一个任务. print(f"任务结果:{obj.result()}") pool.shutdown(wait=True) # shutdown : 让我的主进程等待进程池中的所有子进程都结束任务之后,再执行,有点类似于join # shutdown : 在上一个进程池没有完成所有的任务之前,不允许添加新的任务. # 一个任务是通过一个函数实现的,任务完成了,他的返回值就是函数的返回值. print('===main===')
- 异步调用:
# 异步调用 # 异步调用返回值如何接收? 未解决? from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor import time import random import os def task(i): print(f"{os.getpid()} 开始任务") time.sleep(random.randint(1,3)) print(f"{os.getpid()} 任务结束") return i if __name__ == '__main__': # 异步调用 pool = ProcessPoolExecutor() for i in range(10): pool.submit(task,i) pool.shutdown(wait=True) # shutdown : 让我的主进程等待进程池中的所有子进程都结束任务之后,再执行,有点类似于join # shutdown : 在上一个进程池没有完成所有的任务之前,不允许添加新的任务. # 一个任务是通过一个函数实现的,任务完成了,他的返回值就是函数的返回值. print('===main===')
异步如何取结果:
方式一:
统一回收
# 异步调用 # 方式一: 异步调用,统一回收结果. from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor import time import random import os def task(i): print(f"{os.getpid()} 开始任务") time.sleep(random.randint(1,3)) print(f"{os.getpid()} 任务结束") return i if __name__ == '__main__': # 异步调用 pool = ProcessPoolExecutor() lst = [] for i in range(10): obj = pool.submit(task,i) lst.append(obj) pool.shutdown(wait=True) # shutdown : 让我的主进程等待进程池中的所有子进程都结束任务之后,再执行,有点类似于join # shutdown : 在上一个进程池没有完成所有的任务之前,不允许添加新的任务. # 一个任务是通过一个函数实现的,任务完成了,他的返回值就是函数的返回值. print(lst) for i in lst: print(i.result()) print('===main===') # 统一回收结果: 不能马上收到任何一个已经完成的任务的返回值,只能等待所有的任务全部结束统一返回回收.
3. 异步调用+回调函数
首先以爬虫引入:
浏览器工作原理: 向服务器发送一个请求,服务端验证你的请求,如果正确,给你浏览器返回一个文件,浏览器接收文件,将文件里面的代码渲染成你看到的美丽漂亮的磨样.
什么是爬虫?
- 利用代码模拟一个浏览器,进行浏览器的工作流程得到一堆源代码.
- 对源代码进行数据清洗得到我想要的数据.
import requests response = requests.get("http://www.baidu.com") if response.status_code == 200: print(response.text)
版本一:
# 版本一: from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import time import random import os import requests def task(url): """模拟的就是爬起多个源代码,一定会有IO操作(网络延迟)""" response = requests.get(url) if response.status_code == 200: return response.text def parse(content): """模拟对数据的分析, 一般没有IO""" return len(content) if __name__ == '__main__': # """串行 消耗时间长,不可取""" # ret = task("http://www.baidu.com") # print(parse(ret)) # ret = task('http://www.JD.com') # print(parse(ret)) # # ret = task('http://www.taobao.com') # print(parse(ret)) # # ret = task('https://www.cnblogs.com/jin-xin/articles/7459977.html') # print(parse(ret)) # 开启线程池,并发并行的执行 url_list = [ 'http://www.baidu.com', 'http://www.JD.com', 'http://www.JD.com', 'http://www.JD.com', 'http://www.taobao.com', 'https://www.cnblogs.com/jin-xin/articles/7459977.html', 'https://www.luffycity.com/', 'https://www.cnblogs.com/jin-xin/articles/9811379.html', 'https://www.cnblogs.com/jin-xin/articles/11245654.html', 'https://www.sina.com.cn/', ] pool = ThreadPoolExecutor(4) obj_list = [] for url in url_list: obj = pool.submit(task,url) obj_list.append(obj) pool.shutdown(wait=True) for res in obj_list: print(parse(res.result())) # 版本一的问题: # 1.异步发出多个任务,并发的执行,但是统一的接收所有的任务的返回值.(效率低,不能实时的获取结果) # 2. 分析结果流程是串行的,影响效率.
版本二:
# 版本二: from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor import time import random import os import requests def task(url): """模拟的就是爬起多个源代码,一定会有IO操作(网络延迟)""" response = requests.get(url) if response.status_code == 200: return parse(response.text) def parse(content): """模拟对数据的分析, 一般没有IO""" return len(content) # print(len(content)) if __name__ == '__main__': # 开启线程池,并发并行的执行 url_list = [ 'http://www.baidu.com', 'http://www.JD.com', 'http://www.JD.com', 'http://www.JD.com', 'http://www.taobao.com', 'https://www.cnblogs.com/jin-xin/articles/7459977.html', 'https://www.luffycity.com/', 'https://www.cnblogs.com/jin-xin/articles/9811379.html', 'https://www.cnblogs.com/jin-xin/articles/11245654.html', 'https://www.sina.com.cn/', ] pool = ThreadPoolExecutor(4) obj_list = [] for url in url_list: obj = pool.submit(task, url) obj_list.append(obj) # pool.shutdown(wait=True) for res in obj_list: print(res.result())
现在解决问题的两个方式:
再开一个线程进程池,并发并行的处理,但是再开一个进程线程池的开销
将原来的任务扩大
版本一:
- 线程池设置4个线程,异步发起10个任务,每个任务是通过网页获取源码,并发执行. 最后统一用到列表回收10个任务,串行着分析源码.
版本二:
- 线程池设置4个线程,异步发起10个任务,每个任务是通过网页获取源码 + 数据分析,并发并行,
- 最后将所有的结果展示出来.
- 耦合性增强了
- 并发执行任务,此任务最好是IO阻塞,才能发挥最大的作用.
版本三:
可以为进程池或线程池内的每个进程或线程绑定一个函数,该函数在进程或线程的任务执行完毕后自动触发,并接收任务的返回值当作参数,该函数称为回调函数
# 版本三: # 基于异步调用回收所有任务的结果,并且要做到实时回收结果, # 并发执行任务,每个任务只有处理IO阻塞的,不能增加新功能 # 异步调用 + 回调函数 from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor import time import random import os import requests def task(url): """模拟的就是爬起多个源代码,一定会有IO操作(网络延迟)""" response = requests.get(url) if response.status_code == 200: return response.text def parse(obj): """模拟对数据的分析, 一般没有IO""" print(len(obj.result())) if __name__ == '__main__': # 开启线程池,并发并行的执行 url_list = [ 'http://www.baidu.com', 'http://www.JD.com', 'http://www.JD.com', 'http://www.JD.com', 'http://www.taobao.com', 'https://www.cnblogs.com/jin-xin/articles/7459977.html', 'https://www.luffycity.com/', 'https://www.cnblogs.com/jin-xin/articles/9811379.html', 'https://www.cnblogs.com/jin-xin/articles/11245654.html', 'https://www.sina.com.cn/', ] pool = ThreadPoolExecutor(4) for url in url_list: obj = pool.submit(task, url) obj.add_done_callback(parse) """ 线程池设置4个线程,异步发起10个任务,每个任务是通过网页获取源代码,并发执行, 当一个任务完成之后,将parse这个分析代码的任务交给剩余的空闲的线程去执行,这个完成了获取源代码的线程继续去处理其他任务 如果进程池 + 回调: 回调函数由主进程去执行. 如果线程池 + 回调: 回调函数由空闲的线程去执行. """
Q:
异步和回调时一回事吗?
A:不是一回事,
异步是站在发布任务的角度,一次提交多个任务,然后就直接执行下一行代码.
而回调函数,站在接收结果的角度: 按照顺序接收每个任务的结果,进行下一步处理.
回调函数: 可以为进程池或线程池内的每个进程或线程绑定一个函数,该函数在进程或线程的任务执行完毕后自动触发,并接收任务的返回值当作参数,该函数称为回调函数
异步 + 回调:
异步处理的是IO类型,回调处理的是非IO类型
我们可以把耗时间(阻塞)的任务放到进程池中,然后指定回调函数(主进程负责执行),这样主进程在执行回调函数时就省去了I/O的过程,直接拿到的是任务的结果