zoukankan      html  css  js  c++  java
  • 协程 和 io模型

    # ==========       协程
    # 协程介绍
    # 协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。
    # 一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。、

    #强调的
    #1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,
    #切换其他线程运行)
    #2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率
    # (!!!非io操作的切换与效率无关)


    #对比操作系统控制线程的切换,用户在单线程内控制协程的切换
    # 优点
    #1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
    #2. 单线程内就可以实现并发的效果,最大限度地利用cpu

    #缺点
    #1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
    #2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程


    #总结协程特点:

    # 1 必须在只有一个单线程里实现并发
    # 2 修改共享数据不需加锁
    # 3 用户程序里自己保存多个控制流的上下文栈
    # 4 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

    ## 进程 启动多个进程 进程之间是由操作系统负责调用
    # 线程 启动多个线程 真正被CPU执行的最小单位实际是线程
    # 开启一个线程 创建一个线程 寄存器 堆栈
    # 关闭一个线程
    # 协程
    # 本质上是一个线程
    # 能够在多个任务之间切换来节省一些IO时间
    # 协程中任务之间的切换也消耗时间,但是开销要远远小于进程线程之间的切换
    # 实现并发的手段
    # import time
    # def consumer():
    # while True:
    # x = yield
    # time.sleep(1)
    # print('处理了数据 :',x)
    #
    # def producer():
    # c = consumer()
    # next(c)
    # for i in range(10):
    # time.sleep(1)
    # print('生产了数据 :',i)
    # c.send(i)
    #
    # producer()

    # greenlet 模块
    # greenlet只是提供了一种比generator更加便捷的切换方式,
    # 当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。

    # 真正的协程模块就是使用greenlet完成的切换
    #g1.switch('egon')#可以在第一次switch时传入参数,以后都不需要


    # from greenlet import greenlet
    # def eat():
    # print('eating start')
    # g2.switch()
    # print('eating end')
    # g2.switch()
    #
    # def play():
    # print('playing start')
    # g1.switch()
    # print('playing end')
    # g1 = greenlet(eat)
    # g2 = greenlet(play)
    # g1.switch()
    # from gevent import monkey;monkey.patch_all()
    # import time
    # import gevent
    # import threading
    # def eat():
    # print(threading.current_thread().getName())
    # print(threading.current_thread())
    # print('eating start')
    # time.sleep(1)
    # print('eating end')
    #
    # def play():
    # print(threading.current_thread().getName())
    # print(threading.current_thread())
    # print('playing start')
    # time.sleep(1)
    # print('playing end')
    #
    # g1 = gevent.spawn(eat)
    # g2 = gevent.spawn(play)
    # g1.join()
    # g2.join()


    # gevent 模块
    # 进程和线程的任务切换右操作系统完成

    # 协程任务之间的切换由程序(代码)完成,只有遇到协程模块能识别的IO操作的时候,程序才会进行任务切换,实现并发的效果
    # 上例gevent.sleep(2)模拟的是gevent可以识别的io阻塞,而time.sleep(2)或其他的阻塞,
    # gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了
    # from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前
    # 或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头

    # gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet,
    # 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。


    # 方法
    # g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,
    # 后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的
    # g2=gevent.spawn(func2)
    #g1.join() #等待g1结束
    #g2.join() #等待g2结束
    #或者上述两步合作一步:gevent.joinall([g1,g2])
    #g1.value#拿到func1的返回值

    # 同步 和 异步
    # from gevent import monkey;monkey.patch_all()
    # import time
    # import gevent
    #
    # def task(n):
    # time.sleep(1)
    # print(n)
    #
    # def sync():
    # for i in range(10):
    # task(i)
    #
    # def async():
    # g_lst = []
    # for i in range(10):
    # g = gevent.spawn(task,i)
    # g_lst.append(g)
    # gevent.joinall(g_lst) # for g in g_lst:g.join()
    # sync()
    # async()


    # 协程 : 能够在一个线程中实现并发效果的概念
    # 能够规避一些任务中的IO操作
    # 在任务的执行过程中,检测到IO就切换到其他任务

    # 多线程 被弱化了
    # 协程 在一个线程上 提高CPU 的利用率
    # 协程相比于多线程的优势 切换的效率更快

    # 爬虫的例子
    # 请求过程中的IO等待
    # from gevent import monkey;monkey.patch_all()
    # import gevent
    # from urllib.request import urlopen # 内置的模块
    # def get_url(url):
    # response = urlopen(url)
    # content = response.read().decode('utf-8')
    # return len(content)
    #
    # g1 = gevent.spawn(get_url,'http://www.baidu.com')
    # g2 = gevent.spawn(get_url,'http://www.sogou.com')
    # g3 = gevent.spawn(get_url,'http://www.taobao.com')
    # g4 = gevent.spawn(get_url,'http://www.hao123.com')
    # g5 = gevent.spawn(get_url,'http://www.cnblogs.com')
    # gevent.joinall([g1,g2,g3,g4,g5])
    # print(g1.value)
    # print(g2.value)
    # print(g3.value)
    # print(g4.value)
    # print(g5.value)

    # ret = get_url('http://www.baidu.com')
    # print(ret)

    # socket server



    #通过gevent实现单线程下的socket并发
    #注意 :from gevent import monkey;monkey.patch_all()一定要放到导入socket模块之前,否则gevent无法识别socket的阻塞

    #server 端
    # from gevent import monkey;monkey.patch_all()
    # import socket
    # import gevent
    # def talk(conn):
    # conn.send(b'hello')
    # print(conn.recv(1024).decode('utf-8'))
    # conn.close()
    #
    # sk = socket.socket()
    # sk.bind(('127.0.0.1',8080))
    # sk.listen()
    # while True:
    # conn,addr = sk.accept()
    # gevent.spawn(talk,conn)
    # sk.close()


    #client 端
    # import socket
    # sk = socket.socket()
    # sk.connect(('127.0.0.1',8080))
    # print(sk.recv(1024))
    # msg = input('>>>').encode('utf-8')
    # sk.send(msg)
    # sk.close()


    # ===================== IO 模型

    # IO模型

    #阻塞 非阻塞 io多路复用

    #阻塞
    # 几乎所有的程序员第一次接触到的网络编程都是从listen()、send()、recv() 等接口开始的,
    # 使用这些接口可以很方便的构建服务器/客户机的模型。然而大部分的socket接口都是阻塞型的
    # 实际上,除非特别指定,几乎所有的IO接口 ( 包括socket接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,
    # 如在调用recv(1024)的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。


    # 非阻塞
    #在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有

    # 所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。
    # 但是非阻塞IO模型绝不被推荐。
    # 我们不能否则其优点:能够在等待任务完成的时间里干其他活了
    # (包括提交其他任务,也就是 “后台” 可以有多个任务在“”同时“”执行)。
    #但是也难掩其缺点:
    #1. 循环调用recv()将大幅度推高CPU占用率;这也是我们在代码中留一句time.sleep(2)的原因,否则在低配主机下极容易出现卡机情况
    #2. 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,
    # 而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。
    # 非阻塞 代码 server 端
    # import socket
    # sk = socket.socket()
    # sk.bind(('127.0.0.1',9000))
    # sk.setblocking(False)
    # sk.listen()
    # conn_l = []
    # del_conn = []
    # while True:
    # try:
    # conn,addr = sk.accept() #不阻塞,但是没人连我会报错
    # print('建立连接了:',addr)
    # conn_l.append(conn)
    # except BlockingIOError:
    # for con in conn_l:
    # try:
    # msg = con.recv(1024) # 非阻塞,如果没有数据就报错
    # if msg == b'':
    # del_conn.append(con)
    # continue
    # print(msg)
    # con.send(b'byebye')
    # except BlockingIOError:pass
    # for con in del_conn:
    # con.close()
    # conn_l.remove(con)
    # del_conn.clear()
    # while True : 10000 500 501

    # 非阻塞 client 端
    # import time
    # import socket
    # import threading
    # def func():
    # sk = socket.socket()
    # sk.connect(('127.0.0.1',9000))
    # sk.send(b'hello')
    # time.sleep(1)
    # print(sk.recv(1024))
    # sk.close()
    # for i in range(2):
    # threading.Thread(target=func).start()


    # IO 模型多虑复用
    # 当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,
    # 当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
    # 强调:
    # 1 。如果处理的连接数不是很高的话,使用select / epoll的web
    # server不一定比使用multi - threading + blockingIO的web
    # server性能更好,可能延迟还更大。select / epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
    # 2 。 在多路复用模型中,对于每一个socket,一般都设置成为non - blocking,但是,如上图所示,
    # 整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socketIO给block。
    # 结论: select的优势在于可以处理多个连接,不适用于单个连接

    #该模型的优点:
    #相比其他模型,使用select() 的事件驱动模型只用单线程(进程)执行,占用资源少,
    # 不消耗太多 CPU,同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。

    #该模型的缺点:
    #首先select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,select()接口本身需要消耗大量时间去轮询各个句柄。
    #很多操作系统提供了更为高效的接口,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。
    #如果需要实现更高效的服务器程序,类似epoll这样的接口更被推荐。遗憾的是不同的操作系统特供的epoll接口有很大差异,
    #所以使用类似于epoll的接口实现具有较好跨平台能力的服务器会比较困难。
    #其次,该模型将事件探测和事件响应夹杂在一起,一旦事件响应的执行体庞大,则对整个模型是灾难性的。

    # 代码 server 端
    # import select
    # import socket
    #
    # sk = socket.socket()
    # sk.bind(('127.0.0.1',8000))
    # sk.setblocking(False)
    # sk.listen()
    #
    # read_lst = [sk]
    # while True: # [sk,conn]
    # r_lst,w_lst,x_lst = select.select(read_lst,[],[])
    # for i in r_lst:
    # if i is sk:
    # conn,addr = i.accept()
    # read_lst.append(conn)
    # else:
    # ret = i.recv(1024)
    # if ret == b'':
    # i.close()
    # read_lst.remove(i)
    # continue
    # print(ret)
    # i.send(b'goodbye!')


    # client 端
    # import time
    # import socket
    # import threading
    # def func():
    # sk = socket.socket()
    # sk.connect(('127.0.0.1',8000))
    # sk.send(b'hello')
    # time.sleep(3)
    # print(sk.recv(1024))
    # sk.close()
    #
    # for i in range(20):
    # threading.Thread(target=func).start()


    # 异步IO(Asynchronous I/O)
    #Linux下的asynchronous IO其实用得不多
    #  用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,
    # 当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,
    # kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,
    # 告诉它read操作完成了

    # 代码
    # import selectors
    # from socket import *
    #
    # def accept(sk,mask):
    # conn,addr=sk.accept()
    # sel.register(conn,selectors.EVENT_READ,read)
    #
    # def read(conn,mask):
    # try:
    # data=conn.recv(1024)
    # if not data:
    # print('closing',conn)
    # sel.unregister(conn)
    # conn.close()
    # return
    # conn.send(data.upper()+b'_SB')
    # except Exception:
    # print('closing', conn)
    # sel.unregister(conn)
    # conn.close()
    #
    # sk=socket()
    # sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    # sk.bind(('127.0.0.1',8088))
    # sk.listen(5)
    # sk.setblocking(False) #设置socket的接口为非阻塞
    # sel=selectors.DefaultSelector() # 选择一个适合我的IO多路复用的机制
    # sel.register(sk,selectors.EVENT_READ,accept)
    #相当于网select的读列表里append了一个sk对象,并且绑定了一个回调函数accept
    # 说白了就是 如果有人请求连接sk,就调用accrpt方法

    # while True:
    # events=sel.select() #检测所有的sk,conn,是否有完成wait data阶段
    # for sel_obj,mask in events: # [conn]
    # callback=sel_obj.data #callback=read
    # callback(sel_obj.fileobj,mask) #read(conn,1)
  • 相关阅读:
    搭建高可用mongodb集群—— 副本集
    搭建高可用mongodb集群(一)——mongodb配置主从模式
    单线程异步回调机制的缺陷与node的解决方案
    js实现接口的几种方式
    访问者模式和php实现
    策略模式和php实现
    状态模式和php实现
    观察者模式和php实现
    C语言之一般树
    电子相册之bitmap
  • 原文地址:https://www.cnblogs.com/xuerh/p/8711498.html
Copyright © 2011-2022 走看看