IO多路复用

1 IO多路复用:

    检测多个socket是否发生变化(是否链接成功(可读),或者是否获取到信息(可写))

2.socket服务端默认是在connect,recv,阻塞

3.如何让socket编程不阻塞?

  使用socket模块的setbloking方法,将阻塞的地方全部变成非阻塞,有助于提高单线程并发效率。但是会抛BlockingIOError异常,捕获即可。

4.系统检测socket是否发生了变化有三种方法

  1.select:最多监听1024个socket,循环去检测

  2.poll:不限制监听的次数,循环去检测(水平触发)

  3.epoll:不限制监听的系数,回调方法(边缘触发)

5.提高并发的方案:

  1.多进程:密集型计算

  2.多线程:IO操作

  3.异步非阻塞模块(twisted)基于时间循环的异步非阻塞模块

,scrapy框架(单线程完成并发)

6.什么是异步非阻塞?

  非阻塞:就是不等待socket的setblocking方法

  比如创建socket对某个地址进行connect,获取数据recv时会默认阻塞(链接成功后才会接受到数据),才会执行后边的操作,效率不高,会等待前一个socket执行完了之后才执行后边的socket。

但是设置setblocking(False),connect,和recv就不会等待,但是会报BlockingIOError错,捕获异常即可!

  异步:起通知的作用!(烧水)

  执行完之后会自动执行回调函数,或者自动执行某些功能。比如做爬虫的时候,执行完之后会自动执行回调函数!

7.什么是同步阻塞?

  就是等一个socket执行完之后才会执行下一个socket,按照顺序执行。

单线程解决并发
def get_data(key):
    client=socket.socket()
    client.connect(('www.baidu.com',80))
    client.sendall(b'GET /s?wd=%alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n' )
    chunk_list=[]
    while 1:
        chunk=client.recv(8096)
        if not chunk:
            break
        chunk_list.append(chunk)
    boby=b''.join(chunk_list)
    print(boby.decode('utf8'))
key_list=['alex','sb','db']
for item in key_list:
    get_data(item)

 4.单线程的并发:select    setbloking

import socket
import select#检测socket是否发生了变化

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(('www.oldboyedu.com',80)) except BlockingIOError as e : pass

socket_list
=[client1,client2,client3]#检测是否服务端给我返回数据了 可读 coon_list=[client1,client2,client3]#检测是否所有的socket和服务器链接成功了 可写 while 1: rlist,wlist,elist=select.select(socket_list,coon_list,[],0.005) # wlist表示已经和服务端连接成功 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=fdf HTTP/1.0\r\nhost:www.sogou.com\r\n\r\n') else: sk.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.oldboyedu.com\r\n\r\n') coon_list.remove(sk)

  for sk in rlist: chunk_list=[] while 1: try: chunk=sk.recv(8096) if not chunk:#chunk接收到的数据为空的话,就表示接收完毕 break chunk_list.append(chunk) except BlockingIOError as e: break boby=b''.join(chunk_list) print('--------->',boby.decode('utf8')) # print('--------->',boby) sk.close() socket_list.remove(sk) if not socket_list: break

高级版单线程并发 
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:
                # 发生变换的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)
                # print(body.decode('utf-8'))
                sk.func(body)
                sk.sock.close()
                self.socket_list.remove(sk)
            if not self.socket_list:
                break


def baidu_repsonse(body):
    print('百度下载结果:',body)

def sogou_repsonse(body):
    print('搜狗下载结果:', body)

def oldboyedu_repsonse(body):
    print('老男孩下载结果:', body)


t1 = Nb()
t1.add('www.baidu.com',baidu_repsonse)
t1.add('www.sogou.com',sogou_repsonse)
t1.add('www.oldboyedu.com',oldboyedu_repsonse)
t1.run()
 

 协程:

  协程是由开发人员创造出来的一个不真实存在的东西。

  协程也叫微线程,是对一个线程进行分片,让线程在代码之间相互切换执行,不再是像原来一样按照顺序执行!

单个的协程没有什么意义,无法实现并发,甚至性能会降低,它和IO 操作相结合,能大大的提高效率,会执行greenlet的switch方法!

 

初识协程;

pip3 install greenlet#安装greenlet模块
import
greenlet def f1(): print(11) gr2.switch() print(22) gr2.switch() def f2(): print(33) gr1.switch() print(44) gr1=greenlet.greenlet(f1) gr2=greenlet.greenlet(f2) gr1.switch()

 

协程+IO操作:

from gevent import monkey
monkey.patch_all() # 以后代码中遇到IO都会自动执行greenlet的switch进行切换
import requests
import gevent


def get_page1(url):
    ret = requests.get(url)
    print(url,ret.content)

def get_page2(url):
    ret = requests.get(url)
    print(url,ret.content)

def get_page3(url):
    ret = requests.get(url)
    print(url,ret.content)

gevent.joinall([
    gevent.spawn(get_page1, 'https://www.python.org/'), # 协程1
    gevent.spawn(get_page2, 'https://www.yahoo.com/'),  # 协程2
    gevent.spawn(get_page3, 'https://github.com/'),     # 协程3
])

 

基于yield实现协程

def f1():
    print(11)
    yield
    print(22)
    yield
    print(33)

def f2():
    print(55)
    yield
    print(66)
    yield
    print(77)

v1 = f1()
v2 = f2()

next(v1) # v1.send(None)
next(v2) # v1.send(None)
next(v1) # v1.send(None)
next(v2) # v1.send(None)
next(v1) # v1.send(None)
next(v2) # v1.send(None)

 重点来啦!

进程,线程,协程之间的区别?

  进程是计算机资源分配的最小单元,主要用来数据的隔离。

  线程是计算机工作的最小单元,一个进程可以有多个线程(默认只有一个),一个应用程序有对个进程。

  他们在其他语言中并没有这个概念,在python中,一般IO操作使用多线程,计算密集型计算使用多进程,这主要是由python的GIL锁造成的。

  GIL锁就是在同一时刻同一进程只有一个线程才能被CPU调度,如果想使用计算机多核的优势,那就使用多进程,这就是为什么计算密集型操作使用多进程,而IO操作操作则不占用CPU。

  开发人员让代码更加的强大,就出现了协程,协程本身是不存在的,由程序员自己创造的,协程可以在函数之间相互切换,本身没有太大的意义,但是和IO切换放在一起就特别厉害了,可以让一个线程分片,进而达到一个线程不会停止,一遇到IO就切换到别处,当IO操作完成之后还会切回来。在python中使用协程会遇到一个叫做greenlet的模块实现协程+IO自动切换的模块是gevent.

 

posted @ 2018-09-13 21:11  魏三斗  阅读(539)  评论(0编辑  收藏  举报