多线程(三)

多线程(三)

1.同步/异步and阻塞/非阻塞

进程运行的三种状态:运行,就绪,阻塞

从进程执行的态度:

阻塞:程序运行时,遇到了IO,程序挂起,cpu被切走

非阻塞:程序没有遇到IO,或者程序遇到IO,通过某种手段,让cpu强行运行该程序

从提交任务的角度:

同步:提交一个任务,自任务开始运行直到此任务结束(可能有IO),返回一个返回值之后,再提交下一个任务

异步:一次提交多个任务,然后就直接执行下一行代码

ps:
1. 同步与异步针对的是函数/任务的调用方式:同步就是当一个进程发起一个函数(任务)调用的时候,一直等到函数(任务)完成,而进程继续处于激活状态。而异步情况下是当一个进程发起一个函数(任务)调用的时候,不会等函数返回,而是继续往下执行当,函数返回的时候通过状态、通知、事件等方式通知进程任务完成。

2. 阻塞与非阻塞针对的是进程或线程:阻塞是当请求不能满足的时候就将进程挂起,而非阻塞则不会阻塞当前进程

3.阻塞调用是指调用结果返回之前,当前线程会被挂起(如遇到io操作)。函数只有在得到结果之后才会将阻塞的线程激活。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。

#举例:
1. 同步调用:apply一个累计1亿次的任务,该调用会一直等待,直到任务返回结果为止,但并未阻塞住(即便是被抢走cpu的执行权限,那也是处于就绪态);
2. 阻塞调用:当socket工作在阻塞模式的时候,如果没有数据的情况下调用recv函数,则当前线程就会被挂起,直到有数据为止。

2.同步调用/异步调用

同步调用

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time,random,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(10):
        obj = pool.submit(task,i)
       #obj是一个动态对象,返回的当前对象的状态,可能运行,可能阻塞或结束 
        print(f'任务结果:{obj.result()}')
        #obj.result() 必须等到此任务完成后,得到返回结果,在执行下一个
    pool.shutdown(wait = True)
    #shutdown:主进程等待进程池中的所有子进程结束任务之后,在执行,类似于join
    #wait=True:在上一个进程池没有完成所有的任务之前,不允许添加新的任务
    #一个任务是通过一个函数实现的,任务完成了它的返回值就是函数的返回值
    print('===主')
    
异步调用

1.版本1
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time,random,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(10):
        pool.submit(task,i)
    pool.shutdown(wait =True)
    print('==='主)
    
异步调用,返回值如何接受?这一问题未解决

2.异步调用接收结果
异步调用,统一回收结果
    
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time,random,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)
    l1 = []
    for i in range(10):
        obj = pool.submit(task,i)
        l1.append(obj)
    pool.shutdown(wait=True)
    for i in l1:
        print(i.result())
    print("===主")
    
统一回收结果:不能马上收到任何一个已经完成的任务的返回值,只能等所有的任务全部结束后统一回收

        

3.异步调用+回调函数

浏览器工作原理:向服务端发送一个请求,服务端验证请求,如果正确,返回一个文件,浏览器接收到文件,将文件里面的代码渲染成可视化页面
    
爬虫:
1.利用代码模拟一个浏览器,进行浏览器的工作流程得到网页源代码
2.对源代码进行数据清洗得到想要的数据

版本1:
    
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import tim,random,os,requests

def task(url):
    ret = requests.get(url) #模拟爬取多个源代码,一定有IO阻塞
    if ret.status_code = 200:
        return ret.text
    
def parse(content):
    return len(content) #模拟对数据进行分析,一般没有IO

if __name__ == '__main__':
    url_list = ['http://www.baidu.com',
               'http://www.JD.com',
               'http://www.taobao.com'
               'http://www.sina.com',
               'http://www.gitee.com',
               'http://www.zhihu.com',
               'http://www.7k7k.com',
               'http://www.JD.com',
               'http://www.hao123.com',
               'http://www.4399.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 res in obj_list:
        print(parse(res.result()))   #串行,耗费时间长
        
版本1:
    1.线程池设置4个线程,异步发出10个任务,每个任务是通过网页获取源码,并发的执行,但是统一的接收所有任务的返回值.(效率低,不能实时的获取结果)
    2.分析结果流程是串行,影响效率
    
版本2:
    
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import tim,random,os,requests

def task(url):
    ret = requests.get(url) #模拟爬取多个源代码,一定有IO阻塞
    if ret.status_code = 200:
        return parse(ret.text)
    
def parse(content):
    return len(content) #模拟对数据进行分析,一般没有IO

if __name__ == '__main__':
    url_list = ['http://www.baidu.com',
               'http://www.JD.com',
               'http://www.taobao.com'
               'http://www.sina.com',
               'http://www.gitee.com',
               'http://www.zhihu.com',
               'http://www.7k7k.com',
               'http://www.JD.com',
               'http://www.hao123.com',
               'http://www.4399.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 res in obj_list:
        print(res.result())
    
版本2:
    1.线程池设置4个线程,异步发起10个任务,每个任务是通过网页获取源码+数据分析,并发的执行,最后将所有结果展示出来
    2.耦合性增强了,并发执行任务,此任务最好是IO阻塞,才能发挥最大的效果
    
    
    
版本3:
    基于异步调用,回收所有任务结果:实现实时回收结果,并发执行每个任务只是处理IO阻塞的,不能增加新的功能.
    异步调用+回调函数

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import tim,random,os,requests

def task(url):
    ret = requests.get(url) #模拟爬取多个源代码,一定有IO阻塞
    if ret.status_code = 200:
        return ret.text
    
def parse(obj):
    print(len(obj.result())) #模拟对数据进行分析,一般没有IO

if __name__ == '__main__':
    url_list = ['http://www.baidu.com',
               'http://www.JD.com',
               'http://www.taobao.com'
               'http://www.sina.com',
               'http://www.gitee.com',
               'http://www.zhihu.com',
               'http://www.7k7k.com',
               'http://www.JD.com',
               'http://www.hao123.com',
               'http://www.4399.com'
               ]
    
    pool = ThreadPoolExecutor(4)
    for url in url_list:
        obj = pool.submit(task,url)
 		obj.add_done_callback(parse)
        
版本3:
    线程池设置4个线程,异步发起10个任务,每个任务是通过网页获取源码,并发执行,当一个任务完成之后,将parse这个分析代码的任务交由剩余的空闲的线程去执行,此线程继续去处理其他任务
    如果进程池+回调,回调函数由主进程去执行
    如果线程池+回调,回调函数由空闲的线程去执行
    
异步与回调:
    异步是站在发布任务的角度
    站在接收结果的角度:回调函数(按顺序接收每个任务的结果,进行下一步处理)
        
异步+回调:
    异步处理的IO类型
    回调处理非IO类型
posted @ 2019-08-26 21:38  Bugbiss  阅读(205)  评论(0编辑  收藏  举报