什么是协程(第三方模块gevent--内置模块asyncio)

一:协程

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

image

2.协程的作用?
程序级别的切换,提升效率 单线程下实现并发

让原本是CPU切换线程   变成  程序切换线程
3.安装第三方模块:在命令行下
pip3 install greenlet
pip3 uninstall greenlet  卸载第三方模块
pip3 list   # 列出当前解释器环境下安装的第三方模块

二:greenlet模块(初级模块,实现了保存状态加切换)

首先 先引入的是greenlet模块 然后在引入真正的协程模块
greenlet模块 只是初级模块,只实现了保存状态加切换,没实现遇到io自动切换
  • 代码实现
from greenlet import greenlet


def eat(name):
    print(name,'在吃了一口')
    g2.switch(name)

    print(name,'在吃了第二口')
    # 执行g2,不需要再传参数了,因为已经传过了,从玩了第二下开始执行
    g2.switch()


def play(name):
    print(name, '玩了一下')
    # 执行g1,不需要再传参数了,因为已经传过了,从第二口开始执行
    g1.switch()

    print(name, '玩了第二下')


    
# 类加括号实列化 得到对象
g1=greenlet(eat)
g2=greenlet(play)

# 执行
g1.switch('egon')

三: gevent模块(协程模块)

gevent模块,协程模块,遇到io可以自动切换

image

  • 代码实现
import gevent
import time

def eat(name):
    print(name, '在吃了一口')
    # 遇到了io 自动切换
    # 只能用gevent.sleep
    gevent.sleep(2)
    print(name, '在吃了第二口')

def play(name):
    print(name, '玩了一下')
    # 遇到了io,是gevent的io
    gevent.sleep(3)
    print(name, '玩了第二下')

# 执行前时间
ctime = time.time()
# 内置有返回值  设置协程任务,执行,函数(参数)
res1 = gevent.spawn(eat, 'egon')
res2 = gevent.spawn(play, 'egon')

# # 等待协程执行完,在执行主线程
# res1.join()
# res2.join()
gevent.joinall([res1, res2])  # 相当于上面两句
print('主线程')
print(time.time()-ctime)

image

  • 输出结果
egon 在吃了一口
egon 玩了一下
egon 在吃了第二口
egon 玩了第二下
主线程
3.0269923210144043

1.time 模式协程 遇到io情况

使用原来的time的io,不会切换,并且变成了串行
  • 代码实现
import gevent
import time

def eat(name):
    print(name, '在吃了一口')
    # 遇到了io 自动切换
    # 只能用gevent.sleep
    time.sleep(2)
    print(name, '在吃了第二口')

def play(name):
    print(name, '玩了一下')
    # 遇到了io,是gevent的io
    time.sleep(3)
    print(name, '玩了第二下')

# 执行前时间
ctime = time.time()
# 内置有返回值  设置协程任务,执行,函数(参数)
res1 = gevent.spawn(eat, 'egon')
res2 = gevent.spawn(play, 'egon')

# # 等待协程执行完,在执行主线程
# res1.join()
# res2.join()
gevent.joinall([res1, res2])  # 相当于上面两句
print('主线程')
print(time.time()-ctime)

image

2.解决time无法遇到io切换
  • time无法io切换原因:
线程内切换 用原来的io,会出现它的Gil锁立马就释放了,就切换到另一条线程上执行了,不会再执行异步,而是串行
  • 解决方法:
猴子补丁 : 把原本的io全部替换成gevent的io
  • 猴子补丁作用:
将原本的IO 全部换成不释放Gil锁的IO 所有需要把Gil锁重写一遍 达到遇到io自动切换
  • 代码实现
# 猴子补丁:把原来的io全都替换成gevent的io
from gevent import monkey;monkey.patch_all()
import gevent
import time

def eat(name):
    print(name, '在吃了一口')
    # 遇到了io 自动切换
    # 只能用gevent.sleep
    time.sleep(2)
    print(name, '在吃了第二口')
def play(name):
    print(name, '玩了一下')
    # 遇到了io,是gevent的io
    time.sleep(3)
    print(name, '玩了第二下')
# 执行前时间
ctime = time.time()
# 内置有返回值  设置协程任务,执行,函数(参数)
res1 = gevent.spawn(eat, 'egon')
res2 = gevent.spawn(play, 'egon')

# # 等待协程执行完,在执行主线程
# res1.join()
# res2.join()
gevent.joinall([res1, res2])  # 相当于上面两句
print('主线程')
print(time.time()-ctime)

image

四:协程实现TCP服务端并发的效果

并发效果:一个服务端可以同时服务多个客户端
实现:
多进程下开设多线程 多线程下开设协程
  • 服务器


import socket
from gevent import monkey;monkey.patch_all()
from gevent import spawn
def talk(sock):
    while True:
        try:
            # 固定接收客户端数据
            data = sock.recv(1024)
            # 判断接收数据是否为0
            if data == 0: break
            print(data)
            # 向客户端发送数据
            sock.send(data + b'hello baby!')
            # 异常捕获
        except ConnectionResetError as e:
            print(e)
            # 接收套字节
            sock.close()
            break
def servers():
    # 套字节
    server = socket.socket()
    # 绑定ip与端口
    server.bind(('127.0.0.1', 8080))
    # 连接池
    server.listen()
    # 通信循环
    while True:
        # 被动客户端连接 并返回 sock 通信 接收与发送
        # addr 客户端地址
        sock, addr = server.accept()
        # 执行任务 函数(sock)参数
        spawn(talk, sock)
# 执行封装
g1 = spawn(servers)
g1.join()
  • 客户端
# 客户端开设几百个线程发消息即可

from threading import Thread, current_thread
from socket import *

def client():
    # UDP协议
    client = socket(AF_INET, SOCK_STREAM)
    # 连接服务器ip与port
    client.connect(('127.0.0.1', 8080))
    n = 0
    while True:
        #                        子线程名子 循环加1
        msg = '%s say hello %s' % (current_thread().name, n)
        n += 1
        # 发送数据 编码
        client.send(msg.encode('utf-8'))
        # 固定接收数据
        data = client.recv(1024)
        # 解码
        print(data.decode('utf-8'))

if __name__ == '__main__':
    # 循环执行500个线程
    for i in range(500):
        # 设置任务
        t = Thread(target=client)
        # 执行任务
        t.start()

五: asyncio(内置协程模块)

内置模块   python 3.4 推出这个模块,python作者主导的

import asyncio
import time
import threading
# 这个函数是协程函数
async def task():
    # 获取当前线程的名字
    res=threading.current_thread().getName()
    print(res)
    print('xxx')
    # 只要有IO操作,前面加个await
    await asyncio.sleep(2)
    print('协程执行完成')

async def task2():
    # 获取当前线程的名字
    res=threading.current_thread().getName()
    print(res)
    print('2222')
    await asyncio.sleep(3)
    print('222协程执行完成')

ctime=time.time()
# 拿到一个循环对象
loop=asyncio.get_event_loop()
# 设置任务
tasks=[task(),task2()]
# 执行任务
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
print(time.time()-ctime)

image

posted @ 2022-01-18 22:42  AlexEvans  阅读(155)  评论(0编辑  收藏  举报