协程-协程实现与原理浅析

什么是协程

我刚开始工作的时候,需要调研熟悉freertos,由于学习过程中一直使用Linux,不太了解实时操作系统,所以简单去官网看了下介绍,很快就被协程吸引了注意:
在这里插入图片描述
当时的我只是了解进程和线程,协程是个陌生词汇。查阅一些资料后,获取了如下相关信息,初步认识了一下协程:

  1. 协程也常常被称为,“轻量级线程”、“微线程”、“纤程(fiber)”等,英文词汇常使用coroutine
  2. 协程是用户层面上自行实现的任务调度对象,其与用户态线程非常相似,主要差异是每个协程不被内核抢占式的调度,一般是通过单例调度器去主动create,resume,yield。
  3. 协程适用于 IO 密集型的任务。常见提供原生协程支持的语言有:c++20、go,lua、python 等,其他语言以库的形式提供协程功能,比如 C++20 之前腾讯的 fiber 和 libco ,c语言的ntyco(wangbojing);
  4. 我们可以简单以协程和线程做类比,表面上他们都可以运行你注册上去的回调函数,但是线程(比如posix_thread)需要用内核线程去绑定运行,而协程是由用户态的调度器实例,去自行维护协程切换上下文,他们的并发机理不一样,开销也不同。实际上,协程是对线程资源的进一步代理。
进程线程协程
定义独立的执行单位一个进程中的执行单位在线程内部的执行单位
资源拥有独立的资源空间共享进程的资源空间共享线程的资源空间
调度由操作系统进行调度由线程调度器进行调度由协程调度器进行调度
切换切换开销较大切换开销较小切换开销极小
并发性可以实现真正的并发性(并行)可以实现并发性(内核线程真并行)由协程调度器进行并行调度
通信进程间通信机制(ipc)线程间共享进程虚拟内存空间通过消息传递
同步使用进程间同步机制使用线程同步机制使用协程同步机制
开发成本相对较高中等相对较低

协程有什么用

上面也提到了,协程可以在CPU密集型的多任务处理场景中大展拳脚,他可以把线程的cpu时间代理,细分为多个协程,每个协程分得一点资源去运行自己的回调,也就是充分的利用了cpu。同时整体编程难度约等于同步编程,但是性能却是异步编程的性能,通过非阻塞方式处理任务,提高程序的并发性和响应性。在网络编程、Web开发、爬虫等场景中特别常见。
此处举例ntyco作者(wangbojing)对于协程的调侃:

在做网络 IO 编程的时候 ,有一个非常理想的情况,就是每次 accept 返回的时候 ,就为新来的客户端分配 一个线程 ,这样一个客户端对应线程 。就不会有多个线程共用一sockfd 。每请求线程的方式, 并且代码逻辑非常易读。 但是这只理想 ,线程创建代价调度就呵呵了。先来看一下每请求一个线程的 情况,代码如下 :

while(1) 
{ 
	socklen_t len = sizeof(struct sockaddr_in); 
	int clientfd = accept(sockfd, (struct sockaddr*)&remote, &len); 
	pthread_t thread_id; 
	pthread_create(&thread_id, NULL, client_cb, &clientfd); 
}

这样的做法 ,写完 放到生产环境下面,如果你的老板不打死你,你来找我 来帮你老板,为民除害。如果我们有协程 ,我们就可以这样实现 。参考代码如下:https://github.com/wangbojing/NtyCo/blob/master/nty_server_test.c

while (1) 
{ 
	socklen_t len = sizeof(struct sockaddr_in); 
	int cli_fd = nty_accept(fd, (struct sockaddr*)&remote, &len); 
	nty_coroutine *read_co; 
	nty_coroutine_create(&read_co, server_reader, &cli_fd); 
}

这样的 代码是完全可以放在生产环境下面。如果你 的老板要打死,你来找 我,我帮你把老板打死 ,为民除害 。

通过以上的玩笑,我们可以初步领略一下协程的风采,协程背后的高性能是如何确保的呢?

协程的实现及应用

协程由于要对线程中的cpu时间片进行划分,调度算法且不说是基于抢占式还是完全公平,协程的上下文切换就不得不作为主要考虑的问题,目前协程基于context_switch的区别主要分为以下几种:

  1. longjmp,setjmp(跨栈goto)
  2. ucontext
  3. 汇编操作寄存器实现上下文切换

以neyco为例,该库基于汇编实现寄存器的切换,此处需要补充部分寄存器知识点:
x86_64 的寄存器有16个64位寄存器,分别是:%rax, %rbx, %rcx, %esi, %edi, %rbp, %rsp, %r8, %r9, %r10, %r11, %r12,%r13, %r14, %r15。
其中:

  • %rax 作为函数返回值使用的。
  • %rsp 栈指针寄存器,指向栈顶
  • %rdi, %rsi, %rdx, %rcx, %r8, %r9 用作函数参数,依次对应第1参数,第2参数。。。(所以参数超过六个要借助栈偏移传输,要被吃不少性能)
  • %rbx, %rbp, %r12, %r13, %r14, %r15 用作数据存储,遵循调用者使用规则,换句话说,就是随便用。调用子函数之前要备份它,以防它被修改
  • %r10, %r11 用作数据存储,就是使用前要先保存原值。

上下文切换的时候,通过mov指令,将当前的寄存器状态保存到当前协程实例中,将下一个协程实例的寄存器状态再mov到当前寄存器上。
来源:https://github.com/wangbojing/NtyCo/blob/master

__asm__ (
"    .text                                  \n"
"       .p2align 4,,15                                   \n"
".globl _switch                                          \n"
".globl __switch                                         \n"
"_switch:                                                \n"
"__switch:                                               \n"
"       movq %rsp, 0(%rsi)      # save stack_pointer     \n"
"       movq %rbp, 8(%rsi)      # save frame_pointer     \n"
"       movq (%rsp), %rax       # save insn_pointer      \n"
"       movq %rax, 16(%rsi)                              \n"
"       movq %rbx, 24(%rsi)     # save rbx,r12-r15       \n"
"       movq %r12, 32(%rsi)                              \n"
"       movq %r13, 40(%rsi)                              \n"
"       movq %r14, 48(%rsi)                              \n"
"       movq %r15, 56(%rsi)                              \n"
"       movq 56(%rdi), %r15                              \n"
"       movq 48(%rdi), %r14                              \n"
"       movq 40(%rdi), %r13     # restore rbx,r12-r15    \n"
"       movq 32(%rdi), %r12                              \n"
"       movq 24(%rdi), %rbx                              \n"
"       movq 8(%rdi), %rbp      # restore frame_pointer  \n"
"       movq 0(%rdi), %rsp      # restore stack_pointer  \n"
"       movq 16(%rdi), %rax     # restore insn_pointer   \n"
"       movq %rax, (%rsp)                                \n"
"       ret                                              \n"
);

在这里插入图片描述
当然,此处展示的仅仅是原理上的东西,一个被整个领域盛赞的项目是需要根据需求痛点,结合大大小小技术点去逐步完善的,如果项目中有地方需要使用协程,不妨花时间去阅读源码并结合测试,相信读到此处大家对协程这个概念已经不陌生了,本帖的任务也已经完成,再会。

c/cpp提升

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我叫大魔宝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值