协程 及 libco 介绍
libco 是腾讯开源的一个协程库,主要应用于微信后台RPC框架,下面我们从为什么使用协程、如何实现协程、libco使用等方面了解协程和libco。
why协程
为什么使用协程,我们先从server框架的实现说起,对于client-server的架构,server最简单的实现:
while(1) {accept();recv();do();send();}
串行地接收连接、读取请求、处理、应答,该实现弊端显而易见,server同一时间只能为一个客户端服务。
为充分利用好多核cpu进行任务处理,我们有了多进程/多线程的server框架,这也是server最常用的实现方式:
accept进程 - n个epoll进程 - n个worker进程
- accpet进程处理到来的连接,并将fd交给各个epoll进程
- epoll进程对各fd设置监控事件,当事件触发时通过共享内存等方式,将请求传给各个worker进程
- worker进程负责具体的业务逻辑处理并回包应答
以上框架以事件监听、进程池的方式,解决了多任务处理问题,但我们还可以对其作进一步的优化。
进程/线程是Linux内核最小的调度单位,一个进程在进行io操作时 (常见于分布式系统中RPC远程调用),其所在的cpu也处于iowait状态。直到后端svr返回,或者该进程的时间片用完、进程被切换到就绪态。是否可以把原本用于iowait的cpu时间片利用起来,发生io操作时让cpu处理新的请求,以提高单核cpu的使用率?
协程在用户态下完成切换,由程序员完成调度,结合对socket类/io操作类函数挂钩子、添加事件监听,为以上问题提供了解决方法。
用户态下上下文切换
Linux提供了接口用于用户态下保存进程上下文信息,这也是实现协程的基础:
- getcontext(ucontext_t *ucp): 获取当前进程/线程上下文信息,存储到ucp中
- makecontext(ucontext_t *ucp, void (*func)(), int argc, ...): 将func关联到上下文ucp
- setcontext(const ucontext_t *ucp): 将上下文设置为ucp
- swapcontext(ucontext_t *oucp, ucontext_t *ucp): 进行上下文切换,将当前上下文保存到oucp中,切换到ucp
以上函数与保存上下文的 ucontext_t 结构都在 ucontext.h 中定义,ucontext_t 结构中,我们主要关心两个字段:
- struct ucontext *uc_link: 协程后继上下文
- stack_t uc_stack: 保存协程数据的栈空间
stack_t 结构用于保存协程数据,该空间需要事先分配,我们主要关注该结构中的以下两个字段:
- void __user *ss_sp: 栈头指针
- size_t ss_size: 栈大小
获取进程上下文并切换的方法,总结有以下几步:
- 调用 getcontext(),获取当前上下文
- 预分配栈空间,设置 xxx.uc_stack.ss_sp 和 xxx.uc_stack.ss_size 的值
- 设置后继上下文环境,即设置 xxx.uc_link 的值
- 调用 makecontext(),变更上下文环境
- 调用 swapcontext(),完成跳转
Socket族函数/io异步处理
当进程使用socket族函数 (connect/send/recv等)、io函数 (read/write等),我们使用协程切换任务前,需对相应的fd设置监听事件,以便io完成后原有逻辑继续执行。
对io函数,我们可以事先设置钩子,在真正调用接口前,对相应fd设置事件监听。同样,Linux为我们设置钩子提供了接口,以read()函数为例:
- 编写名字为 read() 的函数,该函数先对fd调用epoll函数设置事件监听
- read() 中使用dlsym(),调用真正的 read()
- 将编写好的文件打包,编译成库文件:gcc -shared -Idl -fPIC prog2.c -o libprog2.so
- 执行程序时引用以上库文件:LD_PRELOAD=/home/qspace/lib/libprog2.so ./prog
当在prog程序中调用 read() 时,使用的就是我们实现的 read() 函数。
对于glibc函数设置钩子的方法,可参考:Let's Hook a Librarg Function
libco
有了以上准备工作,我们可以构建这样的server框架:
accept进程 - epoll进程(n个epoll协程) - n个worker进程(每个worker进程n个worker协程)
该框架下,接收请求、业务逻辑处理、应答都可以看做单独的任务,相应的epoll、worker协程事先分配,服务流程如下:
- mainloop主循环,负责 i/监听请求事件,有请求则拉起一个worker协程处理;ii/如果timeout时间内没有请求,则处理就绪协程(即io操作已返回)
- worker协程,如果遇到io操作则挂起,对fd加监听事件,让出cpu
libco 提供了以下接口:
- co_create: 创建协程,可在程序启动时创建各任务协程
- co_yield: 协程主动让出cpu,调io操作函数后调用
- co_resume: io操作完成后(触发相应监听事件)调用,使协程继续往下执行
socket族函数(socket/connect/sendto/recv/recvfrom等)、io函数(read/write) 在libco的co_hook_sys_call.cpp中已经重写,以read为例:
ssize_t read( int fd, void *buf, size_t nbyte )
{
struct pollfd pf = { };
pf.fd = fd;
pf.events = ( POLLIN | POLLERR | POLLHUP );
int pollret = poll( &pf,,timeout ); /*对相应fd设置监听事件*/
ssize_t readret = g_sys_read_func( fd,(char*)buf ,nbyte ); /*真正调用read()*/
return readret;
}
小结
由最简单的单任务处理,到多进程/多线程(并行),再到协程(异步),server在不断地往极致方向优化,以更好地利用硬件性能的提升(多核cpu的出现、单核cpu性能不断提升)。
对程序员而言,可时常检视自己的程序,是否做好并行与异步,在硬件性能提升时,程序服务能力可不可以有相应比例的提升。
协程 及 libco 介绍的更多相关文章
- 写个百万级别full-stack小型协程库——原理介绍
其实说什么百万千万级别都是虚的,下面给出实现原理和测试结果,原理很简单,我就不上图了: 原理:为了简单明了,只支持单线程,每个协程共享一个4K的空间(你可以用堆,用匿名内存映射或者直接开个数组也都是可 ...
- c协程库libco几点体会
https://www.cnblogs.com/dearplain/p/9820913.html 这里说的是Tencent开源的libco. libco的用途和依赖 主要还是c/c++服务端,相比li ...
- 简单介绍什么是协程及其在ES6中的实现方式
协程,英文名coroutine,是一种执行过程可以被暂停和恢复的方法.各个协程之间相互协作完成一个任务. 让我们来看一个关于发挥协程作用的例子.假定我们有一个生产者和消费者的关系,生产者创建物品并将物 ...
- 基于汇编的 C/C++ 协程 - 切换上下文
在前一篇文章<基于汇编的 C/C++ 协程 - 背景知识>中提到一个用于 C/C++ 的协程所需要实现的两大功能: 协程调度 上下文切换 其中调度,其实在技术实现上与其他的线程.进程调度没 ...
- Python-09-线程、进程、协程、异步IO
0. 什么是线程(thread)? 线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元.一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆 ...
- Python之线程、进程和协程
python之线程.进程和协程 目录: 引言 一.线程 1.1 普通的多线程 1.2 自定义线程类 1.3 线程锁 1.3.1 未使用锁 1.3.2 普通锁Lock和RLock 1.3.3 信号量(S ...
- Python实现基于协程的异步爬虫
一.课程介绍 1. 课程来源 本课程核心部分来自<500 lines or less>项目,作者是来自 MongoDB 的工程师 A. Jesse Jiryu Davis 与 Python ...
- 异步时代-java的协程路在何方
面试官:你知道协程吗? 你:订机票的那个吗,我常用. 面试官:行,你先回去吧,到时候电话联系 ........ 很尴尬,但是事实是,很大一部分的程序员不知道协程是啥玩意,更大一部分的程序员,项目中没用 ...
- yield、greenlet与协程gevent
yield 在说明yield之前,我们了解python中一些概念. 在了解Python的数据结构时,容器(container).可迭代对象(iterable).迭代器(iterator).生成器(ge ...
随机推荐
- Eclipse使用相关配置
问题描述:The word is not correctly spelled问题分析:eclipse校验单词拼写造成,出现在配置文件中,会影响到程序的正常执行解决方案:在eclipse下的Window ...
- odoo 前端页面渲染--数据库管理页面
例子 - 去除登陆页面的Powered by Odoo链接从上面的第3步,我们可以看到,最后登录界面是由'web.login' 模板来显示的,通过odoo的继承方式,我们很容易的就可以去除这个链接,通 ...
- mysql学习记录
干净卸载mysql:https://blog.csdn.net/cxy_summer/article/details/70142322mysql 解压缩版安装说明:https://jingyan.ba ...
- 谷歌搜索技巧(转)https://www.runningcheese.com/google
原文地址: Google是一个非常精密成熟的搜索引擎,其搜索结果的丰富性和准确度较其他搜索引擎都要好,但大多数用户都还只是停留在搜索框中输入一两个关键字,然后点击“搜索”按钮的阶段,这一过程是非常低效 ...
- Codeforces 1083B The Fair Nut and Strings
Description 给定两个由 \('a'\), \('b'\) 组成的字符串 \(a\), \(b\),以及两个整数 \(n\) 和 \(k\) \(n\) 表示字符串 \(a\),\(b\) ...
- JAVA值类型和引用类型的区别
java这两种数据类型分别有哪些? java 中的数据类型分为两大类:值类型(基本数据类型)和引用类型(复合数据类型) 一:值类型: 整数类型(byte,short,int,long) 浮点类 ...
- Maven中阿里云私服配置
在国内maven仓库连接速度太慢 ,虽然对于很多互联网企业和大中型软件公司,建个镜像是分分钟的事.但对于个人开发者确实是个问题.解决办法可以用阿里云的MAVEN私服.有两种方法: 1.在$MAVEN_ ...
- 【机器学习】主成分分析法 PCA (II)
主成分分析法(PAC)的优化——选择主成分的数量 根据上一讲,我们知道协方差为① 而训练集的方差为②. 我们希望在方差尽可能小的情况下选择尽可能小的K值. 也就是说我们需要找到k值使得①/②的值尽可能 ...
- mongodb分组排序
@Override public MessageDto getCheckInMembersByFlight(String fltDt, String fltNr, String channel,Str ...
- 20155312 张竞予 Exp9 Web安全基础
Exp9 Web安全基础 目录 基础问题回答 (1)SQL注入攻击原理,如何防御 (2)XSS攻击的原理,如何防御 (3)CSRF攻击原理,如何防御 实践过程记录 WebGoat准备工作 1.XSS攻 ...