一·协程概念
在同一线程内,一段执行代码过程中,可以中断并跳转到另一段代码中,接着之前中断的地方继续执行。
协程运行状态于多线程类似。
协程优点
无需线程上下文切换,协程避免了无意义的调度,可以提高性能 无需原子操作锁定及同步开销。
方便切换控制流,简化编程模型 。
高并发+高扩展性+低成本,一个cpu支持上万的协程不是问题,很适合用干高并发处理
协程缺点
1·无法利用多核资源。协程的本质是单线程,不能同时将单个cpu的多个核用上,协程需要进程配合才能运行在多cpu上。(不过我们日常编程不会有这个问题,除非是cpu密集型应用)
2·进行阻塞操作(如io时)会阻塞掉整个程序
3·通过进程+协程的方式才可以利用多核资源
协程和线程的区别
它们都是“并发”的手段
并发(concurrency)指的是:在同一个时间段内管理多个任务的执行。
线程:如果有三个线程对象启动,则会创建三个线程对象,
协程:如果有三个协程函数,但是只会有一个线程
异步的小案例
使用asyncio实现异步程序
import time
import asyncio
now=lambda: time.time() # 定义 now() 为当前时间戳
start = now() # 程序开始时间
协程函数
async def fun(i):
asyncio.sleep(1)
print(i)
for i in range(5):
asyncio.run(fun(i))
print(f"当前花费的时间为{now()-start}") #当前时间 - 开始时间 = 花费的时间非异步程序
import time
import asyncio
now=lambda: time.time() # 定义 now() 为当前时间戳
start = now() # 程序开始时间
def fun_1(i):
time.sleep(1)
print(i)
for i in range(5):
a=fun_1(i)
print(f"当前花费的时间为 {now()-start}")
二·python中asyncio库介绍
asyncio是python3.4之后引入的标准库,内置对异步10的支持。
asyncio的编程模型是一个消息循环,我们从asyncio 模块中直接获取一个eventloop的引用,然后把需要执行的协程扔到eventloop中执行,就实现了异步、
1·事件循环(eventloop)
类似于一个死循环的列表
我们在创建一些协程任务的时候,会将任务放到事件循环中,事件循环会对当前存在的任务进行状态的判断
任务的状态有:已完成,未完成
对于已经完成的任务会进行删除,如果是未完成的任务/等待完成的任务会被事件循环调度
如果当前事件循环的任务已经全部完成,则事件循环列表为空,如果为空,则中断循环并退出。
使用eventloop运行协程函数
一个协程函数,它无法直接运行,需要使用eventloop进行运行,下面是协程函数的两种运行方式
demo1
import asyncio
async def work():
print("这是一个协程对象")
async def main():
task=[
asyncio.create_task(work())
]
await asyncio.wait(task)
asyncio.run(main())
#使用create_task能够运行多个协程对象demo2
import asyncio
async def work():
print("这是一个协程对象")
task=[
work()
]
asyncio.run(asyncio,wait(task))2·coroutine
协程协程对象,指一个使用async 关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。2协程对象需要注册到事件循环,由事件循环调用。
3·task任务
一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。
task对象
task对象就是往事件循环添加任务的一个接口
task对象就是往事件循环中加入任务用的。task用于开发调度协程,通过asyncio.create task(协程对象)创建(python3.7之后有这个函数),也可以用asyncio.ensure future(corkutine)和 loop.create task(coroutine)创建一个task。
run until complete的参数是一个future对象,当传入一个协程,其内部会自动封装成task。不建议手动实例化task对象
使用task和gather实现协程并发
demo1:使用wait方法循环遍历的方式并发协程对象
async def work(x):
print(f"当前接收的参数为{x}")
#模拟一个耗时任务
await asyncio.sleep(x)
return f"当前任务的返回值为{x}"
async def main():
#创建是个任务并提交到事件循环
tasks=[asyncio.create_task(work(i)) for i in range(10)]
#tasks是一个列表不能直接await需要使用wait方法迭代列表
#done 和 pending 是两个集合(set 类型),done包含已经完成的任务,pending包含 仍在等待中、尚未
完成的任务
done, pending=await asyncio.wait(tasks)
for result in done:
print(result)demo2:使用gather的方式并发协程对象
async def work(x):
print(f"当前接收的参数为{x}")
#模拟一个耗时任务
await asyncio.sleep(x)
return f"当前任务的返回值为{x}"
async def main():
#创建是个任务并提交到事件循环
tasks=[asyncio.create_task(work(i)) for i in range(10)
# gather用来收集所有已完成的任务的返回值,并且获取到的任务返回值是有顺序的
results=await asyncio.gather(*tasks)
print(results)
asyncio.run(main())4·future对象
代表将来执行或者没有执行的任务的结果,它和task没有本质上的区别(task对象其实是future对象的子类 )
5.async / await关键字的作用
1·暂停当前协程,等待另一个协程执行完毕。
2·直接获取另一个协程的返回值。
await关键字的流程和使用
当协程函数(async def)中执行到 await:
如果后面的任务 需要等待(比如网络请求、io操作、定时器),
那么当前协程会 挂起(暂停执行),
让出控制权 给事件循环(event loop),
其他任务可以继续执行(比如别的协程或事件处理),
一旦等待的操作完成,事件循环会把这个协程 恢复继续执行。
demo
两个协程函数并发执行
python
demo1
import asyncio
async def say_hello(name):
print(f"{name} 开始")
await asyncio.sleep(2)
print(f"{name} 结束")
async def main():
# 并发启动两个协程任务
await asyncio.gather(
say_hello("协程1"),
say_hello("协程2")
)
asyncio.run(main())执行过程解释
协程1和协程2几乎同时启动。遇到
await asyncio.sleep(2)时,不会阻塞程序,而是让出执行权,事件循环调度另一个协程运行。两个协程的
sleep是并发等待的。大约 2秒后,两个协程几乎同时结束。
输出如下(2秒内完成):
copyedit 协程1 开始 协程2 开始 协程1 结束 协程2 结束
对比同步版本
如果不使用 async 和 await,而是用同步 time.sleep(4),每个任务会依次等待,总共要 8秒:(因为逻辑上没有使用并发(task对象))
async def other():
print("this is other")
await asyncio.sleep(4)
print("协程任务完成")
return "这是当前协程任务的返回值"
async def main_1():
print("process is runing")
res1=await other() #它的执行方式是逻辑上的“同步”顺序
res2=await other()
print("当前协程任务的返回值为", res1 , res2)
asyncio.run(main_1())实际执行流程如下:
打印:
process is runing调用
other(),打印:this is otherawait asyncio.sleep(4)→ 程序等待 4秒打印:
协程任务完成返回值赋给
res1再次调用
other(),重复上面的步骤 → 又等 4秒最后打印两次返回值。
总耗时:大约 8秒
三·协程函数和普通函数的区别
# 普通函数
def foo():
return 123
# 协程函数
async def bar():
return 456
foo_obj = foo() # 返回值:123(已执行)
bar_obj = bar() # 返回一个协程对象,还没执行!
核心区别在于:「调用后是否立即执行」
| 类型 | 调用结果 | 是否立即执行 | 可否直接用结果 |
|---|---|---|---|
| 普通函数 | 得到返回值 | ✅ 已执行 | ✅ 可以立即用 |
| 协程函数 | 得到协程对象 | ❌ 未执行 | ❌ 必须 await 或 run |
1·协程对象的使用
(1)使用await的时候
从上面我们能够知道,当你在一个协程函数内调用另一个协程函数的时候,直接调用不加await的话不会运行,只会返回一个协程对象
在协程函数内部调用另一个协程函数时,必须用 await,否则被调用的协程函数不会被执行
(2)使用 asyncio.run的时候
直接调用一个协程函数可以不使用await,可以使用asyncio.run() ,因为asyncio.run()设计用于程序的最高层入口点,它会创建新的事件循环并运行你传入的协程,只能在主线程且无事件循环运行时调用
(3)demo1正确使用await返回数据
import asyncio
async def bar():
numbers="0123456789"
return numbers
async def bar1():
numbers="0123456789"
return numbers
async def bar2():
numbers="0123456789"
return numbers
async def main():
tasks=[asyncio.create_task(bar()),asyncio.create_task(bar1()),asyncio.create_task(bar2())]
result= await asyncio.gather(*tasks)
#asyncio.gather 用于并发运行多个协程,并收集它们的返回值。
print(result)
if __name__=="__main__":
asyncio.run(main())
#这里是程序的最高点asyncio.run()只能在最高点运行
#run会自动创建一个eventloop事件循环,当 main() 协程执行完毕后,事件循环会自动关闭,程序结束。
运行结果:正常返回值

(4)demo2不使用await
import asyncio
async def bar():
numbers="0123456789"
return numbers
async def bar1():
numbers="0123456789"
return numbers
async def bar2():
numbers="0123456789"
return numbers
async def main():
tasks=[asyncio.create_task(bar()),asyncio.create_task(bar1()),asyncio.create_task(bar2())]
result= asyncio.gather(*tasks)
#由于没有使用await所以只会返回协程对象
print(result)
if __name__=="__main__":
asyncio.run(main())
#这里是程序的最高点asyncio.run()只能在最高点运行
#run会自动创建一个eventloop事件循环,当 main() 协程执行完毕后,事件循环会自动关闭,程序结束。
由于没有正确使用await,所以会直接返回一个对象而不是对象里的值

总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论