Python的协程函数有哪些?
协程这玩意儿,说白了就是“能暂停的函数”。
它不是你打个 def 然后 call 一下就走到底了,它可以“等一等”,然后接着来——就像你在点外卖,点完之后不干等着,而是干点别的,等外卖送到再回来继续吃饭。协程就差不多这个逻辑。
你可能早就听说过 async/await,但如果你还没真刀实枪写过协程,那今天就来好好掰扯掰扯
什么是协程函数,它到底怎么用,和普通函数有啥不一样,以及面试中要是被问到该怎么怼回去(不是,是怎么优雅回答)。
什么是协程函数?
在Python里,协程函数是用 async def 定义的。也就是说,只要你在函数前面加上 async,这个函数就变了,不再是普通的函数,而是一个“可以被暂停”的异步协程函数了。
来看个最基础的例子:
import asyncio
asyncdefsay_hello():
await asyncio.sleep(1)
print("Hello")
asyncio.run(say_hello())
这段代码其实比你想象的还高级:
async def是声明协程的标志,Python 看到这个,就知道你写的是“异步协程函数”;await是个暂停点,它告诉解释器:“我这里等一会,先去干别的”;asyncio.sleep(1)就是一个异步操作,模拟的是“我等1秒再继续干”。
注意:你不能直接 say_hello() 就让它跑起来,因为它返回的是个协程对象,不是真正的执行结果。必须通过 asyncio.run() 或事件循环来运行它。
协程和普通函数到底差在哪?
举个例子你就明白了:
defnormal_func():
print("Start")
time.sleep(1)
print("End")
asyncdefcoroutine_func():
print("Start")
await asyncio.sleep(1)
print("End")
这俩函数干的事儿差不多,但区别在于:
normal_func会阻塞主线程,睡觉的时候整个程序都得等;coroutine_func里的await asyncio.sleep(1)是非阻塞的,Python可以“切走”,去干别的,再回来接着干这个。
这也是协程的核心价值:高并发、低资源消耗。
在爬虫、IO密集型应用、Web服务器中,这种能力简直不要太重要。
比如你要请求1000个页面,用线程开销太大,而协程可以1个线程干1000个活儿,谁用谁知道。
那协程函数到底有哪些类型?
这个问题问得有点宽泛,其实协程函数的种类不多,但协程相关的机制挺多,咱们拆开讲:
1. async def 定义的协程函数
这类是最基本的协程函数类型,一般这样写:
asyncdeffetch_data():
await asyncio.sleep(1)
return"data"
只要你用了 async def,它返回的就不是一个函数结果,而是一个“协程对象”:
coro = fetch_data()
print(type(coro)) # <class 'coroutine'>
这个对象需要放到事件循环里去跑,比如:
result = asyncio.run(fetch_data())
print(result)
2. asyncio自带的协程方法
像 asyncio.sleep()、asyncio.gather()、asyncio.create_task() 这些,都是协程玩法里的“原厂配件”。
比如你想并发执行多个任务:
asyncdefmain():
task1 = asyncio.create_task(fetch_data())
task2 = asyncio.create_task(fetch_data())
await task1
await task2
asyncio.run(main())
它们会并发跑,而不是一个等一个执行完。
那生成器、yield 那些是不是协程?
这个是个常见的误区:yield 和协程,看着有点像,但本质不一样。
简单说:
yield是生成器,用于“惰性生成数据”,比如你写个100万行的文件不想一次读完,用yield就很香;async def+await是异步协程,能实现非阻塞逻辑。
Python 3.5之后才有 async/await 的正式语法,在此之前是用 @asyncio.coroutine 和 yield from 模拟异步行为,但现在几乎没人这么写了,过时了属于是。
协程用起来要注意的那些坑
写协程,不能只学写法,还得知道雷区在哪里。
await 只能用在 async 函数里
别想着在普通函数里 await:
defbad_func():
await asyncio.sleep(1) # 报错,SyntaxError
只能在 async def 里用 await。
协程对象得运行,不运行就啥也不干
asyncdeff():
print("Running...")
f() # 这啥也不会输出
你要么 await f(),要么 asyncio.run(f()),它才会真的执行。
协程和多线程哪个更好?
啊这……这题我熟!
我觉得这个问题可以这样回答:
协程和多线程本质是两种并发模型,协程是用户态的,线程是系统级别的。 对于IO密集型任务,比如网络请求、数据库访问,协程通常比线程效率更高,且更节省资源。 但对于CPU密集型任务,协程就帮不上啥忙了,还得靠多线程或多进程配合。 Python因为GIL的存在,多线程并不是真正并行,而协程是“单线程并发”,更适合应对大量IO操作。
然后面试官一般就点点头,往下翻简历了。
那协程能替代多线程吗?
不能完全替代。
协程非常适合高并发的IO操作,比如做一个高并发的接口转发系统、批量抓取网页等等,它能让你的程序像变魔术一样在几百个任务之间切换,还不怎么消耗内存。
但如果你真的是在跑一些数学密集型的、图像处理类的任务,协程就有点拉了,还是得多进程多线程顶上。
怎么理解 Python 的协程?有哪些场景?
这是高频题,一定要练熟!
标准答案我给你整理如下,放心抄,但理解最重要:
答:
协程是 Python 中用于实现并发的一种方式,它基于事件循环,通过 async def 定义,await 进行挂起和恢复。
协程最大的优势是避免线程开销,在单线程中实现高并发,尤其适合 IO 密集型任务,比如网络请求、爬虫、接口转发等。
使用协程时,必须配合 asyncio 模块,常用的写法包括 asyncio.run(), await, asyncio.gather() 等。
与多线程不同,协程是非抢占式调度,也就是任务执行时不会被打断,只有在遇到 await 时才会让出控制权,因此调度更加可控,资源消耗更低。
但协程并不能提升 CPU 密集型任务的效率,遇到此类任务仍需考虑多进程或使用 C 扩展等手段。
我觉得协程写得顺手之后,简直就是代码里的魔法棒,效率高到飞起,还写得清清爽爽,谁用谁知道。
你说现在面试不问协程的?那真是你面试的公司还没整明白asyncio怎么香。
对编程、职场感兴趣的同学,大家可以联系我微信:golang404,拉你进入“程序员交流群”。
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》。