基础概念
1. 定义:
纤程,微线程。是允许在不同入口点不同位置暂停或开始的计算机程序,简单来说,协程就是可以暂停执行的函数。
2. 协程原理 :
记录一个函数的上下文,协程调度切换时会将记录的上下文保存,在切换回来时进行调取,恢复原有的执行内容,以便从上一次执行位置继续执行。协程本质上就是一个线程,以前多线程任务的切换是由操作系统控制的,遇到I/O阻塞就自动切换,现在我们用协程的目的就是较少操作系统切换的开销(开关线程,创建寄存器、堆栈等,在他们之间进行切换等),在我们自己的程序(应用层面)里面来控制任务的切换。对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。
3. 协程优缺点
优点
【1】协程完成多任务占用计算资源很少
【2】由于协程的多任务切换在应用层完成,因此切换开销少,最大限度地利用cpu
【3】协程为单线程程序,无需进行共享资源同步互斥处理
缺点
协程的本质是一个单线程,无法利用计算机多核资源
标准库协程的实现
python3.5以后,使用标准库asyncio和async/await 语法来编写并发代码。asyncio库通过对异步IO行为的支持完成python的协程。虽然官方说asyncio是未来的开发方向,但是由于其生态不够丰富,大量的客户端不支持awaitable需要自己去封装,所以在使用上存在缺陷。更多时候只能使用已有的异步库(asyncio等),功能有限。
示例:
import asyncio
async def fun1():
print("Start1")
# 遇到阻塞跳出
await asyncio.sleep(3)
print("end1")
async def fun2():
print("Start2")
# 遇到阻塞跳出
await asyncio.sleep(2)
print("end2")
cor1 = fun1()
cor2 = fun2()
tasks = [asyncio.ensure_future(cor1),
asyncio.ensure_future(cor2)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
# Start1
# Start2
# end2
# end1
第三方协程模块
【1】greenlet模块
安装 : sudo pip3 install greenlet
函数:
g = greenlet.greenlet(func)
功能:创建协程对象
参数:协程函数
返回值:greenlet 协程函数对象
g.switch()
功能:选择要执行的协程函数
"""
协程行为展示
"""
from greenlet import greenlet
def fun1():
print("执行fun1")
# 跳转执行fun2
gr2.switch()
print("结束fun1")
# 跳回fun2继续往下执行,而不是从fun2第一行开始
gr2.switch()
def fun2():
print("执行fun2")
# 跳回fun1继续往下执行,而不是从fun1第一行开始
gr1.switch()
print("结束fun2")
# 将函数变为协成函数
gr1 = greenlet(fun1)
gr2 = greenlet(fun2)
gr1.switch()
【2】gevent模块
安装:sudo pip3 install gevent
函数:
gevent.spawn(func,argv)
功能: 生成协程对象
参数:func 协程函数
argv 给协程函数传参(不定参)
返回值: 协程对象
gevent.joinall(list,[timeout])
功能: 阻塞等待协程执行完毕
参数:list 协程对象列表
timeout 超时时间
gevent.sleep(sec)
功能: gevent睡眠阻塞
参数:睡眠时间
* gevent协程只有在遇到gevent指定的阻塞行为时才会自动在协程之间进行跳转,如gevent.joinall(),gevent.sleep()带来的阻塞
monkey脚本
作用:
在gevent协程中,协程只有遇到gevent指定类型的阻塞才能跳转到其他协程,因此,我们希望将普通的IO阻塞行为转换为可以触发gevent协程跳转的阻塞,以提高执行效率。
转换方法:
gevent 提供了一个脚本程序monkey,可以修改底层解释IO阻塞的行为,将很多普通阻塞转换为gevent阻塞。
使用方法:
【1】 导入monkey
from gevent import monkey
【2】 运行相应的脚本,例如转换socket中所有阻塞
monkey.patch_socket()
【3】 如果将所有可转换的IO阻塞全部转换则运行all
monkey.patch_all()
【4】 注意:脚本运行函数需要在对应模块导入前执行
示例:
"""
基于协程的tcp并发
"""
import gevent
from gevent import monkey
monkey.patch_socket() # 执行脚本修改socket阻塞行为
from socket import *
def handle(c):
while True:
data = c.recv(1024).decode() # 属于协程阻塞,不影响其他客户端
if not data:
return
print(data)
c.send(b'OK')
# 创建套接字
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('127.0.0.1', 8888))
s.listen(5)
# 循环接收来自客户端请求
while True:
c, addr = s.accept()
print("Connect from", addr)
# handle(c) # 循环方案
gevent.spawn(handle, c) # 协程方案