博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
24 IO多路复用and异步非阻塞and协程
阅读量:5121 次
发布时间:2019-06-13

本文共 4801 字,大约阅读时间需要 16 分钟。

#先来记下概念性东西

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

  同步:按顺序执行

  阻塞:等待

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

  非阻塞:不等待

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

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

  

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

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

  原始代码如下:

import socketimport selectclient1 = socket.socket()client1.setblocking(False)try:    client1.connect(('www.baidu.com', 80))except BlockingIOError as e:    passclient2 = socket.socket()client2.setblocking(False)try:    client2.connect(('www.sougou.com', 80))except BlockingIOError as e:    passclient3 = socket.socket()client3.setblocking(False)try:    client3.connect(('so.m.sm.cn', 80))except BlockingIOError as e:    passsocket_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\r\nhost:www.baidu.com\r\n\r\n')        elif sk == client2:            sk.sendall(b'GET /web?query=alex HTTP/1.0\r\nhost:www.sogou.com\r\n\r\n')        else:            sk.sendall(b'GET /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-8import socketimport selectclass 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\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:                breakdef 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 greenletdef 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)  # 协程gr1gr2 = greenlet.greenlet(f2)   # 协程gr2gr1.switch()

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

from gevent import monkeymonkey.patch_all()  # 下面代码中遇到IO都会自动执行greenlet的switch进行切换import requestsimport geventdef 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框架)本质差不太多。

 

转载于:https://www.cnblogs.com/cbslock/p/11388853.html

你可能感兴趣的文章
二维码解析,生成
查看>>
不回头的旅行
查看>>
python_字典
查看>>
六、linux目录结构知识
查看>>
C与汇编混合编程
查看>>
BZOJ 百题纪念!
查看>>
教你看懂网上流传的60行JavaScript代码俄罗斯方块游戏
查看>>
清空数据库表sysobjects
查看>>
解决ie9浏览网页以ie7样式问题
查看>>
响应式编程 akka
查看>>
逻辑编程入门--clojure.core.logic
查看>>
小玩意
查看>>
AngularJS的循环输出
查看>>
linux 利用nethogs查看某进程的网卡流量
查看>>
MAC下的反编译、反汇编和调试神器Hopper Disassembler
查看>>
.net Stream篇(六)
查看>>
301 MovedPermanently 重定向
查看>>
转:ubuntu 备份系统
查看>>
Update Bits
查看>>
jira与readmine区别
查看>>