zoukankan      html  css  js  c++  java
  • 24 IO多路复用and异步非阻塞and协程

    #先来记下概念性东西

      IO多路复用作用:检测所有IO请求(主要是socket)是否已经发生变化(是否已经连接成功/是否已经获取数据)(可读/可写)

      同步:按顺序执行

      阻塞:等待

      异步:执行完成之后自动执行回掉函数或自动执行某些操作(通知)

      非阻塞:不等待

      协程:本身是个没什么用的东西,一般跟IO操作一起使用。协程的作用就是进行分片,使得线程在代码快之间按照你的需求来切换

      执行,而不是原来的逐行执行。

      

    1、如果我们要用实现socket的并发请求。除了用多线程和多进程的。单线程也能实现:

      1、使用基于事件循环的IO多路复用+非阻塞,也可以达到并发效果。分别访问三个网址,拿到返回(如果拿到后有后续操作那么就是异步了)。这里用到select模块

      原始代码如下:

    import socket
    import select
    
    client1 = socket.socket()
    client1.setblocking(False)
    try:
        client1.connect(('www.baidu.com', 80))
    except BlockingIOError as e:
        pass
    
    client2 = socket.socket()
    client2.setblocking(False)
    try:
        client2.connect(('www.sougou.com', 80))
    except BlockingIOError as e:
        pass
    
    client3 = socket.socket()
    client3.setblocking(False)
    try:
        client3.connect(('so.m.sm.cn', 80))
    except BlockingIOError as e:
        pass
    
    socket_list = [client1, client2, client3]
    conn_list = [client1, client2, client3]
    while True:
        rlist,wlist,elist = select.select(socket_list, conn_list, [], 0.005)
        '''
        一共四个参数。
        第一个参数。socket_list,检测服务端是否返回数据---可读
        第二个参数。conn_list,检测其中的socket是否已经和服务端连接成功---可写
        第三个参数。[],用来检测异常
        第四个参数。每次检测时间间隔
        '''
        for sk in wlist:
            if sk == client1:
                sk.sendall(b'GET /s?wd=alex HTTP/1.0
    host:www.baidu.com
    
    ')
            elif sk == client2:
                sk.sendall(b'GET /web?query=alex HTTP/1.0
    host:www.sogou.com
    
    ')
            else:
                sk.sendall(b'GET /s?q=alex HTTP/1.0
    host:so.m.sm.cn
    
    ')
            conn_list.remove(sk)
    
        for sk in rlist:
            chunk_list = []
            while True:
                try:
                    chunk = sk.recv(8096)
                    if not chunk:
                        break
                    chunk_list.append(chunk)
                except BlockingIOError as e:
                    break
            body = b''.join(chunk_list)
            print('-------->', body)
            #print(body.decode('utf-8'))
            sk.close()
            socket_list.remove(sk)
    
        if not socket_list:
            break

      封装版本如下:

    # coding:utf-8
    import socket
    import select
    
    
    class Req(object):
        def __init__(self, sk, func):
            self.sock = sk
            self.func = func
    
        def fileno(self):
            return self.sock.fileno()
    
    
    class NB(object):
    
        def __init__(self):
            self.conn_list = []
            self.socket_list = []
    
        def add(self, url, func):
            client = socket.socket()
            client.setblocking(False)  # 非阻塞
            try:
                client.connect((url, 80))
            except BlockingIOError as e:
                pass
            obj = Req(client, func)
            self.conn_list.append(obj)
            self.socket_list.append(obj)
    
        def run(self):
            while True:
                rlist,wlist,elist = select.select(self.socket_list, self.conn_list, [], 0.005)
                # wlist中表示已经连接成功的req对象
                for sk in wlist:
                    # sk:发生变化的req对象
                    sk.sock.sendall(b'GET /s?wd=alex HTTP/1.0
    host:www.baidu.com
    
    ')
                    self.conn_list.remove(sk)
    
                for sk in rlist:
                    chunk_list = []
                    while True:
                        try:
                            chunk = sk.sock.recv(8096)
                            if not chunk:
                                break
                            chunk_list.append(chunk)
                        except BlockingIOError as e:
                            break
                    body = b''.join(chunk_list)
                    sk.func(body)
                    sk.sock.close()
                    self.socket_list.remove(sk)
    
                if not self.socket_list:
                    break
    
    
    def baidu_response(body):
        print('百度下载的结果:', body)
    
    
    def sogou_response(body):
        print('搜狗下载的结果:', body)
    
    
    def so_response(body):
        print('uc下载的结果:', body)
    
    
    t1 = NB()
    t1.add('www.baidu.com', baidu_response)
    t1.add('www.sogou.com', sogou_response)
    t1.add('so.m.sm.cn', so_response)
    t1.run()

      注意点:

        1、socket阻塞之后要捕获BlockingIOError异常。

        2、对比同步阻塞。异步非阻塞请求发过去之后,继续发下一个请求,不会等待消耗时间。然后回来的时候排队。但是比起多线程还是差距挺大的。

    2、协程:

      单独协程用到greenlet模块

      如下简单demo

    import greenlet
    
    
    def f1():
        print(11)  # 1、打印这里
        gr2.switch()
        print(22)   # 3、打印这里
        gr2.switch()
    
    
    def f2():
        print(33)   # 2、打印这里
        gr1.switch()
        print(44)    # 4、打印这里
    
    
    gr1 = greenlet.greenlet(f1)  # 协程gr1
    gr2 = greenlet.greenlet(f2)   # 协程gr2
    gr1.switch()

      所以说单独的协程没用。下面是配合IO切换来使用。这里用到了monkey模块

    from gevent import monkey
    monkey.patch_all()  # 下面代码中遇到IO都会自动执行greenlet的switch进行切换
    import requests
    import gevent
    
    def get_page_1(url):
        ret = requests.get(url)
        print(url, ret.content)
    
    
    def get_page_2(url):
        ret = requests.get(url)
        print(url, ret.content)
    
    
    def get_page_3(url):
        ret = requests.get(url)
        print(url, ret.content)
    
    gevent.joinall([
        gevent.spawn(get_page_1, 'https://www.python.org/'),  # 协程1
        gevent.spawn(get_page_2, 'https://www.yahoo.com/'),  # 协程2
        gevent.spawn(get_page_3, 'https://www.github.com/'),  # 协程3
    ])

      这里就比单独来回访问三次要快得多。由上面的例子可得。协程+IO切换和基于事件循环的IO多路复用(有个Twisted框架)本质差不太多。

  • 相关阅读:
    P2533 [AHOI2012]信号塔
    P1452 Beauty Contest
    P3194 [HNOI2008]水平可见直线
    P2924 [USACO08DEC]大栅栏Largest Fence
    P2742 【模板】二维凸包 / [USACO5.1]圈奶牛Fencing the Cows
    P4208 [JSOI2008]最小生成树计数
    P4280 [AHOI2008]逆序对
    P3199 [HNOI2009]最小圈
    P3343 [ZJOI2015]地震后的幻想乡
    剪刀,石头,布,小游戏脚本
  • 原文地址:https://www.cnblogs.com/cbslock/p/11388853.html
Copyright © 2011-2022 走看看