协程Coroutine(一)
协程是现代编程语言的一重要特性,是一种比线程更加轻量级的存在。C#,python等语言都包含了协程功能,而C++也在其最新的标准C++20中引入了协程这一特性。
1. 协程的介绍
参考:https://www.jianshu.com/p/6dde7f92951e
协程不是被操作系统内核所管理的,而是完全由程序所控制,也就是在用户态执行。这样带来的好处是性能大幅度的提升,因为不会像线程切换那样消耗资源。
协程不是进程也不是线程,而是一个特殊的函数,这个函数可以在某个地方挂起,并且可以重新在挂起处外继续运行。所以说,协程与进程、线程相比并不是一个维度的概念。
一个进程可以包含多个线程,一个线程也可以包含多个协程。简单来说,一个线程内可以由多个这样的特殊函数在运行,但是有一点必须明确的是,一个线程的多个协程的运行是串行的。如果是多核CPU,多个进程或一个进程内的多个线程是可以并行运行的,但是一个线程内协程却绝对是串行的,无论CPU有多少个核。毕竟协程虽然是一个特殊的函数,但仍然是一个函数。一个线程内可以运行多个函数,但这些函数都是串行运行的。当一个协程运行时,其它协程必须挂起。
进程、线程、协程的对比
- 协程既不是进程也不是线程,协程仅仅是一个特殊的函数,协程它进程和进程不是一个维度的。
- 一个进程可以包含多个线程,一个线程可以包含多个协程。
- 一个线程内的多个协程虽然可以切换,但是多个协程是串行执行的,只能在一个线程内运行,没法利用CPU多核能力。
- 协程与进程一样,切换是存在上下文切换问题的。
上下文切换
-
进程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户是无感知的。进程的切换内容包括页全局目录、内核栈、硬件上下文,切换内容保存在内存中。进程切换过程是由“用户态到内核态到用户态”的方式,切换效率低。
-
线程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户无感知。线程的切换内容包括内核栈和硬件上下文。线程切换内容保存在内核栈中。线程切换过程是由“用户态到内核态到用户态”, 切换效率中等。
-
协程的切换者是用户(编程者或应用程序),切换时机是用户自己的程序所决定的。协程的切换内容是硬件上下文,切换内存保存在用户自己的变量(用户栈或堆)中。协程的切换过程只有用户态,即没有陷入内核态,因此切换效率高。
2. Python中的实现
python中可以用yield和send实现协程。
- 程序一旦执行到yield就会停在该处,并且将其返回值进行返回。
- send()方法具有两种功能:第一,传值,send()方法,将其携带的值传递给yield,注意,是传递给yield,而不是x,然后再将其赋值给x;第二,send()方法具有和next()方法一样的功能,也就是说,传值完毕后,会接着上次执行的结果继续执行,直到遇到yield停止。
下例用send和yield实现了一个生产者消费者模型。
1 def consumer(name): 2 print('Consumer start...') 3 while True: 4 print('[consumer]%s need production'%name) 5 bone = yield 6 print('[consumer]%s consumed a production %s'%(name,bone)) 7 8 9 def producer(obj1): 10 obj1.send(None) 11 for i in range(3): 12 print('[producer] is producing production: %s'%i) 13 obj1.send(i) 14 15 16 if __name__ == '__main__': 17 con1 = consumer('ConsumerA') 18 producer(con1)
在3.7以上的python中,则可以用asynio的方式实现协程,实现很像javascript中的await和async
1 import asyncio 2 import time 3 4 async def say_after(delay, what): 5 await asyncio.sleep(delay) 6 print(what) 7 8 async def main(): 9 print(f"started at {time.strftime('%X')}") 10 11 await say_after(1, 'hello') 12 await say_after(2, 'world') 13 14 print(f"finished at {time.strftime('%X')}") 15 16 asyncio.run(main())
此外,asynio提供了更多协程有关方法来丰富使用,详情参考官方文档。
3. Javascript的协程
查阅资料后发现原来Javascript中也有yield关键字。
Javascript中用生成器函数function*实现协程,例子:
1 function* anotherGenerator(i) { 2 yield i + 1; 3 yield i + 2; 4 yield i + 3; 5 } 6 7 function* generator(i) { 8 yield i; 9 yield* anotherGenerator(i); // 移交执行权 10 yield i + 10; 11 } 12 13 var gen = generator(10); 14 15 console.log(gen.next().value); // 10 16 console.log(gen.next().value); // 11 17 console.log(gen.next().value); // 12 18 console.log(gen.next().value); // 13 19 console.log(gen.next().value); // 20
生成器函数的yield
关键字有可以交出函数的执行权,挂起自身,然后 JavaScript 引擎去执行这个函数后面的语句,在上面这个例子中,第 15 行调用gen.next()
开始执行生成器函数的内容,第一次while
循环里yield
交出了执行权,JavaScript 引擎转而执行第 16 行,再次调用gen.next()
,这时候 JavaScript 接着上次挂起的地方执行。
协程属于ESMC6的标准,而常用的async,await是ESMC7的标准更新,所以还是推荐使用async, await来实现协程,更符合现代JS代码规范。
4. C++中的协程
c++直到C++20才引入协程,添加了co_wait, co_yield, co_return关键字来实现
一个简单例子:
1 #include <chrono> 2 #include <iostream> 3 #include <future> 4 #include <experimental/coroutine> 5 6 using namespace std::chrono_literals; 7 8 std::future<int> foo() { 9 std::cout << "call foo\n"; 10 std::this_thread::sleep_for(3s); 11 co_return 5; 12 } 13 14 std::future<std::future<int>> bar() { 15 std::cout << "call bar\n"; 16 std::cout << "before foo\n"; 17 auto n = co_await std::async(foo); 18 std::cout << "after foo\n"; 19 co_return n; 20 } 21 22 int main(int argc, char *argv[]) 23 { 24 std::cout << "before bar\n"; 25 auto i = bar(); 26 std::cout << "after bar\n"; 27 i.wait(); 28 std::cout << "result = " << i.get().get(); 29 return 0; 30 }
(注意带协程的项目要加编译选项 -foroutine)
由于协程在C++20中属于std::experimental的范畴中,并不推荐广泛使用。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具