day37
阻塞、非阻塞、同步、异步
进程运行的三个状态:运行、就绪、阻塞
执行的角度
- 阻塞:程序运行时,遇到了IO,程序挂起,CPU被切走
- 非阻塞:程序没有遇到IO,程序遇到IO但是我通过某种手段,让CPU强行运行我的程序
提交任务的角度
同步:提交一个任务,自任务开始运行直到此任务结束(可能有IO),返回一个返回值之后,我再提交下一个任务
异步:一次提交多个任务,然后直接执行下一行代码,等待任务结果
返回结果如何回收?
案例:给三个老师发布任务:
- 同步:先告知第一个老师完成写书的任务,我从原地等待,等他两天之后完成了,告诉完事了,我再发布下一个任务。。。。
- 异步:直接将三个任务告知三个老师,我就忙我的,直到三个老师完成之后,告知我
同步调用、异步调用
同步调用
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(4) for i in range(6): obj = pool.submit(task, i) # obj是一个动态对象,返回当前对象的状态,有可能运行中(running),可能pending(就绪或阻塞),还可能使结束了(finished returned int) # obj.result()必须等到这个任务完成后,返回结果之后再执行下一个任务 print(obj.result()) # obj.result()没有返回值 pool.shutdown(wait=True) print("===主")
异步调用
异步调用返回值如何接收? 未解决
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(4) for i in range(6): pool.submit(task, i) pool.shutdown(wait=True) # shutdown:让我的主进程等待进程池中所有的子进程都结束之后再执行下面的代码,优点类似于join # shutdown:在上一个进程池没有完成所有的任务之前,不允许添加新的任务 # 一个任务是通过一个函数实现的,任务完成了他的返回值就是函数的返回值 print("===主")
方式一:异步调用统一接收结果
缺点:我不能马上收到任何一个已经完成的任务的返回值,我只能等到所有的任务全部结束之后统一回收
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(4) lst = [] for i in range(6): obj = pool.submit(task, i) lst.append(obj) pool.shutdown() for i in lst: i.result() # print(i.result()) print("===主")
第二种方式:异步调用+回调函数
异步调用+回调函数
浏览器工作原理
- 向服务端发送一个请求,服务端验证你的请求,如果正确,给你的浏览器返回一个文件
- 浏览器接收到文件,将文件里面的代码渲染成你看到的漂亮美丽的模样
爬虫的工作原理
利用代码模拟一个浏览器,进行浏览器的工作流程得到一堆源代码
对源代码进行数据清洗得到我想要的数据
页面请求的状态值
分别有:200请求成功、303重定向、400请求错误、401未授权、403禁止访问、404文件未找到、500服务器错误
代码
import requests ret = requests.get("http://www.baidu.com") if ret.status_code == 200: print(ret.text)
版本一
主代码
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import requests def task(url): """ 模拟的是爬取多个源代码,一定有IO操作 :param url: :return: """ ret = requests.get(url) if ret.status_code == 200: return ret.text def parse(content): """ 模拟对数据进行分析,一般没有IO :param content: :return: """ return len(content) if __name__ == '__main__': # 串行 耗费时间长,不可取 # ret1 = task("http://www.baidu.com") # print(parse(ret1)) # ret2 = task("http://www.JD.com") # print(parse(ret2)) # ret3 = task("http://www.taobao.com") # print(parse(ret3)) # ret4 = task("https://www.cnblogs.com/jin-xin/articles/7459977.html") # print(parse(ret4)) # 开启线程池,并发并行的执行 url_list = [ "http://www.baidu.com", "http://www.JD.com", "http://www.taobao.com", "https://www.cnblogs.com/jin-xin/articles/7459977.html", 'https://www.cnblogs.com/jin-xin/articles/7459977.html', 'https://www.cnblogs.com/jin-xin/articles/7459977.html', 'https://www.cnblogs.com/jin-xin/articles/7459977.html', 'https://www.cnblogs.com/jin-xin/articles/9811379.html', 'https://www.cnblogs.com/jin-xin/articles/11245654.html', 'https://www.luffycity.com/' ] pool = ThreadPoolExecutor(4) obj_list = [] for url in url_list: obj = pool.submit(task, url) obj_list.append(obj) pool.shutdown(wait=True) for i in obj_list: print(parse(i.result())) print("===主")
总结:
缺点:
异步发出10个任务,并发的执行,但是统一接收了所有任务的返回值(效率低,不能实时的获取结果)
分析结果流程是串行,影响了效率
for res in obj_list: print(parse(res.result()))
版本二
针对版本一的缺点2进行改进,让串行变成并发或并行
解决方式
- 再开一个线程进程池,并发并行的处理,再开一个线程进程池开销大
- 将原来的任务扩大 (可以)
主代码
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import requests def task(url): """ 模拟的是爬取多个源代码,一定有IO操作 :param url: :return: """ ret = requests.get(url) if ret.status_code == 200: return parse(ret.text) def parse(content): """ 模拟对数据进行分析,一般没有IO :param content: :return: """ return len(content) if __name__ == '__main__': url_list = [ "http://www.baidu.com", "http://www.JD.com", "http://www.taobao.com", "https://www.cnblogs.com/jin-xin/articles/7459977.html", 'https://www.cnblogs.com/jin-xin/articles/7459977.html', 'https://www.cnblogs.com/jin-xin/articles/7459977.html', 'https://www.cnblogs.com/jin-xin/articles/7459977.html', 'https://www.cnblogs.com/jin-xin/articles/9811379.html', 'https://www.cnblogs.com/jin-xin/articles/11245654.html', 'https://www.luffycity.com/' ] pool = ThreadPoolExecutor(4) obj_list = [] for url in url_list: obj = pool.submit(task, url) obj_list.append(obj) pool.shutdown(wait=True) for i in obj_list: print(i.result()) print("===主")
总结:
版本一与版本二对比
版本一:
- 线程池设置4个线程,异步发起10个任务,每个任务是通过网页获取源码+数据分析,并发执行
- 最后统一用列表回收10个任务,串行着分析源码
版本二:
- 线程池设置4个线程,异步发起10个任务,每个任务是通过网页获取源码+数据分析,并发执行
- 最后把所有的结果展示出来
缺点:
- 耦合性增强了
- 并发执行任务,此任务最好是IO阻塞,才能发挥最大的效果
- 获取源代码肯定有IO阻塞,清洗数据大部分没有IO阻塞,应该要分开不能组在一起
版本三
基于异步调用回收所有任务的结果我要做到实时回收结果
并发执行任务每个任务只是处理IO阻塞的,不能增加新的功能
异步调用+回调函数
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import requests def task(url): """ 模拟的是爬取多个源代码,一定有IO操作 :param url: :return: """ ret = requests.get(url) if ret.status_code == 200: return ret.text def parse(obj): """ 模拟对数据进行分析,一般没有IO :param content: :return: """ print(len(obj.result())) if __name__ == '__main__': url_list = [ "http://www.baidu.com", "http://www.JD.com", "http://www.taobao.com", "https://www.cnblogs.com/jin-xin/articles/7459977.html", 'https://www.cnblogs.com/jin-xin/articles/7459977.html', 'https://www.cnblogs.com/jin-xin/articles/7459977.html', 'https://www.cnblogs.com/jin-xin/articles/7459977.html', 'https://www.cnblogs.com/jin-xin/articles/9811379.html', 'https://www.cnblogs.com/jin-xin/articles/11245654.html', 'https://www.luffycity.com/' ] pool = ThreadPoolExecutor(4) for url in url_list: obj = pool.submit(task, url) obj.add_done_callback(parse)
总结:
- 线程池设置4个线程,异步发起10个任务,每个任务是通过网页获取源码,并发执行
- 当一个任务完成之后,将parse这个分析代码的任务交由剩余的空闲的线程去执行,你这个线程继续去处理其他任务
- 如果是进程池+回调:回调函数由主进程去执行
- 如果是线程池+回调:回到函数由空闲的线程去执
异步与回调是一回事?
- 异步:站在发布任务的角度,处理的是有IO阻塞的代码
- 回调函数:站在接收结果的角度,按顺序接收每个任务的结果,进行下一步处理。处理没有IO阻塞的代码