首页 > 其他 > 详细

24 IO多路复用and异步非阻塞and协程

时间:2019-08-21 16:15:14      阅读:83      评论:0      收藏:0      [点我收藏+]

#先来记下概念性东西

  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(bGET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n)
        elif sk == client2:
            sk.sendall(bGET /web?query=alex HTTP/1.0\r\nhost:www.sogou.com\r\n\r\n)
        else:
            sk.sendall(bGET /s?q=alex HTTP/1.0\r\nhost:so.m.sm.cn\r\n\r\n)
        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(bGET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n)
                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框架)本质差不太多。

 

24 IO多路复用and异步非阻塞and协程

原文:https://www.cnblogs.com/cbslock/p/11388853.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!