python 协程

1. 协程

协程,又称微线程,纤程。英文名Coroutine。
https://www.cnblogs.com/coder-qi/p/10163416.html

协程不是计算机提供的,是人为创造的上下文切换技术,也可以被称为微线程。简而言之 其实就是在一个线程中实现代码块相互切换执行。
可以采用以下几种基于协程的方式:

  • greenlet。
  • yield 关键字
  • asyncio 装饰器(py3.4之后引入)
  • async、await关键字(py3.5之后引入)【推荐】

1.1. greenlet 实现协程

# greenlet是第三方模块需先引入
pip3 install greenlet
# -*- coding: utf-8 -*-
from greenlet import greenlet

def func1():
    print(1)        # 第二步:输出1
    gr2.switch()    # 第三步:切换到 func2 函数
    print(2)        # 第六步:输出2
    gr2.switch()    # 第七步:切换到func2 函数(如果不切换的话句柄会继续往下执行,也就不会进入func2 输出4)

def func2():
    print(3)        # 第四步:输出3
    gr1.switch()    # 第五步:切换到func1 函数
    print(4)        # 第八步:输出4,func2函数 执行完毕句柄继续往下执行

def func3():
    print(5)        # 第十步:输出5

gr1 = greenlet(func1)  # 此处只是生成 greenlet 包装的 func1 对象,代码并不会实际运行
gr2 = greenlet(func2)  # 此处生成 greenlet 包装的 func2 对象

gr1.switch()  			# 第一步:此处是正式执行 func1() 对象
func3() 	 			# 第九步:实例化 func3

运行结果:

1
3
2
4
5

1.2. yield 关键字

不推荐,实际应用场景比较少。
实例1:

def func1():
    yield 1
    yield from func2()  # 这里其实相当于for item in func2(): yield item
    yield 2

def func2():
    yield 3
    yield 4

for item in func1():
    print(item)

运行结果

1
3
4
2

实例2:
参考:https://www.cnblogs.com/coder-qi/p/10163416.html

import time

def test1():
    index=0
    while True:
        #print("--test1--")
        yield f"test1 {index}"
        time.sleep(0.5)
        index += 1

def test2():
    index=0
    while True:
        #print("--test2--")
        yield f"test2 {index}"
        time.sleep(0.5)
        index += 1

if __name__ == "__main__":
    t1 = test1()
    t2 = test2()
    while True:
        print(next(t1))
        print(next(t2))

运行结果:

test1 0
test2 0
test1 1
test2 1
test1 2
test2 2
test1 3
test2 3

1.3. asyncio 模块

参考:https://www.cnblogs.com/micheryu/p/15779377.html
在 python3.4 及之后的版本才可使用,这个框架使用事件循环来编排回调和异步任务。事件循环位于事件循环策略的上下文中。
下图是协程,事件循环和策略之间的相互作用
asyncio
注意:asyncio 牛逼在于遇到 IO 阻塞自动切换!

1.3.1. 使用 @asyncio.coroutine 装饰器

下面我们使用 @asyncio.coroutine 装饰器(py3.10+会移除) 定义了两个协程函数。(基于生成器的协程)

import asyncio

@asyncio.coroutine
def func1():
    print(1)
    # 此处用asyncio.sleep(2)来模拟IO耗时(asyncio.sleep也是一个协程对象,不能用time.sleep()),asyncio定义的协程函数遇到IO操作时
会自动切换到事件循环中的其他任务
    yield from asyncio.sleep(2)
    print(2)

# 暂未使用
@asyncio.coroutine
def func2():
    print(3)
    yield from asyncio.sleep(2)
    print(4)

print(asyncio.iscoroutine(func1()))

loop = asyncio.get_event_loop()
loop.run_until_complete(func1())

运行结果:

/*/demo.py:4: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
  def func1():
/*/demo.py:11: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
  def func2():
True
/*/demo.py:18: DeprecationWarning: There is no current event loop
  loop = asyncio.get_event_loop()
1
2

PS:如果 py 版本高于3.8依然可以使用 asyncio.coroutine 装饰器但是会有告警建议你使用 async & await 关键字来定义协程函数,不会影响使用!
协程函数并不能像普通函数一样直接实例化运行,调用协程函数协程并不会开始运行,只是返回一个协程对象。可以通过 asyncio.iscoroutine 来验证是否是协程对象。

print(asyncio.iscoroutine(func1()))  # True

协程对象必须在事件循环中运行,我们可以通过 asyncio.get_event_loop 方法来获取当前正在运行的循环实例。如 loop 对象,然后把协程对象交给 loop.run_until_complete,协程对象随后会在 loop 里得到运行。
run_until_complete 是一个阻塞(blocking)调用,直到协程运行结束,它才返回;所以他必须接受的是一个可等待对象(协程,任务 和future对象)。

1.3.2. 可等待对象

协程对象:协程函数实例化后就是协程对象
future 对象:asyncio.futures.Future 对象用来链接 底层回调式代码和高层异步/等待式代码,可以简单理解为 future 对象是可以使程序 hang在某个地方等待有结果了之后再继续执行。

1.3.2.1. 创建 future 对象:loop.create_future()
import asyncio

async def main():
	# 获取当前事件循环
	loop = asyncio.get_running_loop()
	# 单单只是创建一个future对象
	fut = loop.create_future()
	# future对象因为什么都没做也就没返回值,所以 await 会一直等待下去程序就会 hang 住
	await fut

asyncio.run(main())
# 这句永远都不会执行
print(1)
1.3.2.2. future 对象.set_result() 方法
async def func(fut):
	fut.set_result("finish")

async def main():
	# 获取当前事件循环
	loop = asyncio.get_running_loop()
	# 单单只是创建一个future对象
	fut = loop.create_future()
	# 创建一个task对象,绑定了func函数并且把我们创建的fut对象传递给了协程对象func;func协程函数内部又对fut对象设置了result
	await loop.create_task(func(fut))
	# 由于设置了fut对象的结果,下面的await就能拿到结果 所以程序就可以继续往下执行了
	print(await fut)

asyncio.run(main())
print(1)

运行结果:

finish
1

任务:

1.4. async & await 关键字【推荐🌟】

参考:https://www.cnblogs.com/micheryu/p/15779377.html
py3.5 及之后版本
本质上和3.4的 asyncio 一致,但更强大。
3.5之后 yield from 不可以在 async 定义的函数内使用,需使用 await。
实例1:
参考:https://blog.csdn.net/u011089760/article/details/90542894

import asyncio
import time

async def job(t):                   # async 形式的功能
    print('Start job ', t)
    # 等待 "t" 秒, 期间切换其他任务。
    # 此处用 asyncio.sleep(t) 来模拟 IO 耗时(asyncio.sleep 也是一个协程对象,不能用 time.sleep())
    # asyncio 定义的协程函数遇到 IO 操作时会自动切换到事件循环中的其他任务
    await asyncio.sleep(t)         
    print('Job ', t, ' takes ', t, ' s')

async def main(loop):                       # async 形式的功能
    tasks = [
        loop.create_task(job(t)) for t in range(1, 4)
    ]                                       # 创建任务, 但是不执行
    await asyncio.wait(tasks)               # 执行并等待所有任务完成

t1 = time.time()
loop = asyncio.get_event_loop()             # 建立 loop
loop.run_until_complete(main(loop))         # 执行 loop,并且等待所有任务结束
loop.close()                                # 关闭 loop
print("Async total time : ", time.time() - t1)

运行结果:

Start job  1
Start job  2
Start job  3
Job  1  takes  1  s
Job  2  takes  2  s
Job  3  takes  3  s
Async total time :  3.0052335262298584

实例2:

import asyncio

async def func1():
	print(1)
	await asyncio.sleep(2)  # 遇到IO自动切换任务;等待2秒, 期间切换其他任务
	print(2)

async def func2():
	print(3)
	await asyncio.sleep(2)  # 此处又遇到IO阻塞后,又会自动切换到tasks中其他的任务
	print(4)

tasks = [
	asyncio.ensure_future(func1()),  # 把协程对象包转成一个 future 对象
	asyncio.ensure_future(func2())
]

loop = asyncio.get_event_loop()
# 执行单个协程函数
loop.run_until_complete(func1())  # 由于func1中使用了await关键字所以此处等同于asyncio.wait
""" 输出结果为:
1
等待2s
2
"""
# 执行多个协程函数
loop.run_until_complete(asyncio.wait(tasks))
""" 输出结果为:
1
3
等待2s
2
4
"""

注:python3.7 之后可以不需要自己获取 loop 对象,可以直接调用 asyncio.run() 方法内部已经帮我们获取了 loop 对象和调用loop.run_until_complete(),但是不支持同时运行多个协程对象。

asyncio.run(func1()) 

async & await 关键字简化代码的同时并且兼容基于生成器的老式协程。

@asyncio.coroutine
def old_style_coroutine():
    yield from asyncio.sleep(1)

async def main():
    await old_style_coroutine()

一个协程函数中可以使用多次 await 关键字。

import asyncio

async def func():
	print("start")
	await asyncio.sleep(5)
	print("end")
	return "finish"

async def main():
	print("执行main方法")
	resp1 = await func()
	print(f"第一次返回值:{resp1}")
	resp2 = await func()
	print(f"第二次返回值:{resp2}")

asyncio.run(main())

运行结果:

执行main方法
start
end
第一次返回值:finish
start
end
第二次返回值:finish

同样我们也可以在一个协程函数中获取多个其他协程对象的返回值:

import asyncio

async def func():
    print(1)
    await asyncio.sleep(2)
    print(2)
    return f"func was done"

async def main():
    print("main开始")
    # 创建协程,将协程封装到Task对象中并添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。
    # 在调用
    task_list = [
        asyncio.create_task(func(), name="n1"),
        asyncio.create_task(func(), name="n2")
    ]
    print("main结束")
    # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。
    # 此处的await是等待所有协程执行完毕,并返回两个集合 done、pending。done存放已完成的task对象,pending存放未完成的task对象
    # 如果设置了timeout值,则意味着此处最多等待的秒,完成的协程返回值写入到done中,未完成则写到pending中。
    done, pending = await asyncio.wait(task_list, timeout=None)
    print("done", done)
    print("pending", pending)

    for coroi in done:
        print(coroi.result())
    #print(list(done)[0].result())

asyncio.run(main())

运行结果:

main开始
main结束
1
1
2
2
done {<Task finished name='n1' coro=<func() done, defined at /*/demo.py:3> result='func was done'>, <Task finished name='n2' coro=<func() done, defined at /*/demo.py:3> result='func was done'>}
pending set()
func was done
func was done

1.5. 协程任务调用普通函数

普通函数调用协程函数:

async def func3():
	print('i`m func3')
	await asyncio.sleep(2)
	print('func3 finished')

def func4():
	print('i`m func4')
	asyncio.run(func3())
	print('func4 finished')

func4()

"""输出结果:
i`m func4
i`m func3
等待2s
func3 finished
func4 finished
"""

协程函数调用普通函数

def func5():
	print('i`m func5')
	time.sleep(2)
	print('func5 finished')

async def func6():
	print('i`m func6')
	func5()
	print('func6 finished')

asyncio.run(func6())
print('all finish')
"""
i`m func6
i`m func5
等待2s
func5 finished
func6 finished
all finish
"""

1.6 实例

  1. async 定义的函数是协程函数,调用协程函数返回协程对象
  2. await 可以处理可等待对象,可等待对象有协程对象(corotine)、任务对象(task,用于顶层操作,必须作用于协程对象)和未来对象(future,底层操作 task 的父类)
  3. 协程的核心是事件循环,目的是合理的调度可等待对象的执行逻辑从而优化io等待时间
  4. 事件循环的最小调度单元是任务,通过主动的将协程对象封装成的协程任务并注册到事件循环并通过内部的调度算法调度执行
  5. 运行协程函数需要使用特定的接口调用如 asyncio.run(main()),它会启动一个事件循环并将 main() 作为任务注册到事件循环中调度执行

参考:(没有分析完)
https://blog.csdn.net/weixin_44689630/category_8872679.html
https://blog.csdn.net/weixin_44689630/article/details/122509483
https://blog.csdn.net/peng78585/article/details/128342847

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值