> 技术文档 > 协程和线程(python中异步的概念以及asyncio库)_python协程和线程区别

协程和线程(python中异步的概念以及asyncio库)_python协程和线程区别

目录

一·协程概念

协程优点:

协程缺点:

协程和线程的区别

使用asyncio实现异步程序

 非异步程序

二·python中asyncio库介绍

1·事件循环(EventLoop)

使用EventLoop运行协程函数

demo1

demo2 

2·coroutine

3·task任务

task对象

使用task和gather实现协程并发

Demo2:使用gather的方式并发协程对象

4·future对象

5.async / await关键字

await关键字的使用

demo

🧾 对比同步版本

⏱ 实际执行流程如下:

三·协程函数和普通函数的区别

1·协程对象的使用

(1)使用await的时候

(2)使用 asyncio.run的时候

(4)demo2不使用await


一·协程概念

在同一线程内,一段执行代码过程中,可以中断并跳转到另一段代码中,接着之前中断的地方继续执行。

协程运行状态于多线程类似。

协程优点:

无需线程上下文切换,协程避免了无意义的调度,可以提高性能 无需原子操作锁定及同步开销。

方便切换控制流,简化编程模型 。

高并发+高扩展性+低成本,一个CPU支持上万的协程不是问题,很适合用干高并发处理

协程缺点:

1·无法利用多核资源。协程的本质是单线程,不能同时将单个CPU的多个核用上,协程需要进程配合才能运行在多CPU上。(不过我们日常编程不会有这个问题,除非是CPU密集型应用)

2·进行阻塞操作(如IO时)会阻塞掉整个程序

3·通过进程+协程的方式才可以利用多核资源

协程和线程的区别

✅ 它们都是“并发”的手段

并发(Concurrency)指的是:在同一个时间段内管理多个任务的执行

线程:如果有三个线程对象启动,则会创建三个线程对象,

协程:如果有三个协程函数,但是只会有一个线程

异步的小案例

使用asyncio实现异步程序

import timeimport asyncionow=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 timeimport asyncionow=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 asyncioasync def work(): print(\"这是一个协程对象\")async def main(): task=[ asyncio.create_task(work()) ] await asyncio.wait(task)asyncio.run(main())#使用create_task能够运行多个协程对象

demo2 

import asyncioasync 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

两个协程函数并发执行

pythondemo1import asyncioasync 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 结束

🧾 对比同步版本

如果不使用 asyncawait,而是用同步 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())

⏱ 实际执行流程如下:

  1. 打印:process is runing

  2. 调用 other(),打印:this is other

  3. await asyncio.sleep(4) → 程序等待 4秒

  4. 打印:协程任务完成

  5. 返回值赋给 res1

  6. 再次调用 other(),重复上面的步骤 → 又等 4秒

  7. 最后打印两次返回值。

🧠 总耗时:大约 8秒

三·协程函数和普通函数的区别

# 普通函数def foo(): return 123# 协程函数async def bar(): return 456foo_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 asyncioasync def bar(): numbers=\"0123456789\" return numbersasync def bar1(): numbers=\"0123456789\" return numbersasync def bar2(): numbers=\"0123456789\" return numbersasync 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 asyncioasync def bar(): numbers=\"0123456789\" return numbersasync def bar1(): numbers=\"0123456789\" return numbersasync def bar2(): numbers=\"0123456789\" return numbersasync 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,所以会直接返回一个对象而不是对象里的值