Python协程梳理

该博客围绕Python展开,介绍了协程底层的生成器,包括手动切换模块greenlet和自动切换模块gevent。还详细阐述了阻塞IO和非阻塞IO在server端的情况,以及IO多路复用在cilent端和server端的应用,包括实现并发和封装select的库selectors的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1协程底层–生成器

#一种实现协程的方法--生成器yield
def producer():
    while True:
        i = yield 99999
        print("第%s个包子做好了"%i)
def consumer():
    r=producer()
    r.__next__()
    count=1
    while count<10:
        r.send(count)
        print("第%s个人吃了第%s个包子"%(count,count))
        count+=1
consumer()

#额外发现的一个注意点:不能直接生成器函数点来用,这样的话它每次都是重新开始

2.协程集成手动切换(switch)模块greenlet

"""
greenlet是一个用c实现的协程模块(第三方库),相比于puthon自带的yield,
它可以使你在任意函数之前随意切换,而不需要把这个函数先声明为genertor
"""
from greenlet import greenlet
def t1():
    print("1111")
    gr2.switch()
def t2():
    print("2222")
gr1=greenlet(t1)
gr2=greenlet(t2)
gr1.switch()
"""
手动控制切换,但在有大量未知IO操作时不适用,所以还有一个自动切换模块
"""

3.协程自动切换模块gevent

"""
在一个协程执行中遇到IO阻塞自动切换到另一个协程
"""
import gevent
import requests,time
start=time.time()
def foo(url):
    print("Get:%s"%url)
    reap=requests.get(url)
    date=reap.text
    print("%s bytes received from %s"%(len(date),url))
gevent.joinall([
    gevent.spawn(foo,"https://www.youkuaiyun.com/"),
    #创建一个协程,并附加自动切换功能
    gevent.spawn(foo,"https://www.zhipin.com/?ka=header-home"),
    gevent.spawn(foo,"https://www.cnblogs.com/linhaifeng/articles/6204014.html")
])
#joinall 是在这些协程结束后继续执行
print("cost time:",time.time()-start)

4 阻塞IO和非阻塞IO——server端

"""
阻塞IO,之前写的很多默认的都是阻塞IO,可以通过修改参数等变成非阻塞,
同时要用异常处理,让程序不报错
比如socket里面的accept,recv等
多线程队列里的put,get
"""
#这里列举socket非阻塞

import time
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.bind(('127.0.0.1',6667))
sk.listen(5)
sk.setblocking(False)                 #参数默认Ture,不用自己加。改为False之后,阻塞变阻塞
try:
    print ('waiting client connection .......')
    connection,address = sk.accept()   # 进程主动轮询
    print("+++",address)
    client_messge = connection.recv(1024)
    print(str(client_messge,'utf8'))
    connection.close()
except Exception as e:
    print (e)
    time.sleep(4)
"""
优点:能够在等待任务完成的时间里干其他活了
(包括提交其他任务,也就是 “后台” 可以有多个任务在同时执行)。

缺点:任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,
而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。
"""

5 IO多路复用——cilent端

import socket

sk=socket.socket()

sk.connect(("127.0.0.1",9904))

while 1:
    inp=input(">>").strip()
    sk.send(inp.encode("utf8"))
    data=sk.recv(1024)
    print(data.decode("utf8"))

5 IO多路复用——server端

import socket
import select
sk=socket.socket()
sk.bind(("127.0.0.1",9904))
sk.listen(5)
while True:
    r,w,e=select.select([sk,],[],[],5)  #([监听的输入],[监听的输入],[监听的reeor],[轮循监视的时间间隔],)
    """
    ???在有一个客户端连接的时候,为什么不调用accept,会反复print?
    因为select是高水平触发(也就是模电数电里的高电平。其他的还有下降沿,上升沿触发和低电平触发)
    我的理解:select是监视内核态里有没有它的监视对象的对应数据,
    如果有,它会把这个数据返回给前面的变量,没有则不返回
    """
    for i in r:
        # conn,add=i.accept()
        #print(conn)
        print("hello")
    print('>>>>>>')


6 IO多路复用实现并发–cilent端

#***********************client.py

import socket
sk=socket.socket()
sk.connect(('127.0.0.1',8801))

while True:
    inp=input(">>>>")
    sk.sendall(bytes(inp,"utf8"))
    data=sk.recv(1024)
    print(str(data,'utf8'))

6 IO多路复用实现并发–server端

#***********************server.py
"""
单线程IO多路复用实现并发,同时监听多个文件描述符
之前第一个是只能允许一个客户端连接,循环聊天
第二个是能允许back_lock个客户端连接,但也只能同时和一个客户端循环聊天,其他客户端被挂起
第三个是借助socketserver来实现多线程并发,同时连接多个并循环聊天
"""
import socket
import select
import time
sk=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.bind(("127.0.0.1",8801))
sk.listen(5)
inputs=[sk,]
while True:
    r,w,e=select.select(inputs,[],[],5)
    for obj in r:
        if obj==sk:
            conn,add=obj.accept()
            inputs.append(conn)
        else:
            data_byte=obj.recv(1024)
            print(str(data_byte,'utf8'))
            inp=input('回答%s号客户>>>'%inputs.index(obj))
            obj.sendall(bytes(inp,'utf8'))
            time.sleep(20)
    print("这里可以做其他事情")
#可以实现同样更优效果的还有poll和epoll,select有连接数限制,而poll没有,两者实现机制(轮循)相同
#而epoll用的是其他机制,最高效
#详见博客https://www.cnblogs.com/yuanchenqi/articles/5722574.html

7 封装select的库selectors–cilent端

from socket import *
sk=socket(AF_INET,SOCK_STREAM)
sk.connect(("127.0.0.1",8090))
while True:
    inp=input(">>>")
    sk.send(inp.encode("utf8"))
    date=sk.recv(1024)
    print(date.decode("utf8"))

7 封装select的库selectors–server端

import selectors
import socket
sel = selectors.DefaultSelector()       #根据操作系统选择最优IO多路复用方式
def accept(sock, mask):
    conn, addr = sock.accept()  # Should be ready
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)
def read(conn, mask):
    try:
        data = conn.recv(1024)  # Should be ready
        conn.send(data)  # Hope it won't block
    except Exception:
        sel.unregister(conn)
        conn.close()
sock = socket.socket()
sock.bind(('localhost', 8090))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)  #注册,监听对象和监听对象有变化后返回值要执行的函数
while True:
    events = sel.select()
    for key, mask in events:
        callback = key.data       #key.date是监视对象有变化时返回的函数地址
        callback(key.fileobj, mask)   #key。fileobj是返回监视的有变化的对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值