基于Libtask进行协程浅析
2016-12-07 22:34
344 查看
协程介绍
与子例程一样,协程也是一种程序组件。 相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。 协程源自Simula和Modula-2语言,但也有其他语言支持。 协程更适合于用来实现彼此熟悉的程序组件,如合作式多任务,迭代器,无限列表和管道。 –维基百科下面我们会以Libtask(Go语言的作者之一Russ Cox的作品)作为分析案例来解释协程的原理。
协程工作原理
要了解协程的工作原理,可以从以下几点入手:1、上下文切换。 2、函数调用原理。 3、Libtask保存寄存器值的结构体。 4、Libtask的接口函数和实现。
1、上下文切换
当一个程序被执行(称为进程)的时候,这些寄存器的值通常会被修改。所以当要切换进程执行的时候,只需要把这些寄存器的值保存下来,然后把新进程寄存器的值赋值到CPU中(我们知道CPU的使命就是执行程序中的指令,而且CPU内部有很多用于存放数据的寄存器,其中比较重要的一个寄存器叫EIP寄存器,它用于存储下一条要执行的指令。除了EIP寄存器之外,还有一个比较重要的寄存器叫ESP寄存器,它用于保存程序的栈顶位置。除此之外,CPU还有很多其他用途的寄存器,如:通用寄存器EAX、EDX和段寄存器CS、DS等等。),那么就完成进程切换了,通常我们把这个过程称为上下文切换,协程的切换也类似。
2、函数的调用原理
以C语言为例,函数调用时通过栈结构来保存现场和恢复现场的。比如,在第189行有这样一段代码来进行函数调用:
demoFunc(a, b, c, d);那么,第190行代码的地址会被放在栈底,然后,实参从右往左一次入栈。这样一来,当该函数完毕,程序又会恢复到之前调用处(189行)的下一行(190行)。原理如下图(图片来自百度):
3、Libtask保存寄存器值的结构体
前面说过,要进行上下文切换,存储对应寄存器的值是必不可少的。Libtask通过引进
struct mcontext这个结构体来保存对应的寄存器的值,以下是
struct mcontext这个结构体的源码实现,命名比较规范,感兴趣的读者,可以通过名称查询对应寄存器的功能。
struct mcontext { int mc_gs; int mc_fs; int mc_es; int mc_ds; int mc_edi; int mc_esi; int mc_ebp; int mc_isp; int mc_ebx; int mc_edx; int mc_ecx; int mc_eax; int mc_trapno; int mc_err; int mc_eip; int mc_cs; int mc_eflags; int mc_esp; int mc_ss; };
4、Libtask的接口函数和实现。
参考上文提到的函数调用的切换,Libtask提供了三个接口来实现协程的切换。
1)通过
int getcontext(struct mcontext *ctx);获取当前上下文(也就是将对应寄存器的值,存入
struct mcontext结构对应的变量中):
gexcontext: movl 4(%esp), %eax movl %fs, 8(%eax) movl %es, 12(%eax) movl %ds, 16(%eax) movl %ss, 76(%eax) movl %edi, 20(%eax) movl %esi, 24(%eax) movl %ebp, 28(%eax) movl %ebx, 36(%eax) movl %edx, 40(%eax) movl %ecx, 44(%eax) movl $1, 48(%eax) movl (%esp), %ecx movl %ecx, 60(%eax) leal 4(%esp), %ecx movl %ecx, 72(%eax) movl 44(%eax), %ecx movl $0, %eax ret
2)通过
int setcontext(struct mcontext *ctx);来设置上下文(也就是将
struct mcontext结构对应变量的值赋给对应寄存器):
setcontext: movl 4(%esp), %eax movl 8(%eax), %fs movl 12(%eax), %es movl 16(%eax), %ds movl 76(%eax), %ss movl 20(%eax), %edi movl 24(%eax), %esi movl 28(%eax), %ebp movl 36(%eax), %ebx movl 40(%eax), %edx movl 44(%eax), %ecx movl 72(%eax), %esp pushl 60(%eax) movl 48(%eax), %eax ret
3)通过
int swapcontext(struct mcontext *new, struct mcontext *old)来进行上下文的切换,这个实现是基于上文提到的两个函数,实现比较简单:
int swapcontext(struct mcontext *new, struct mcontext *old) { getcontext(old); setcontext(new); return 0; }
个人总结
为什么协程会被引入对于一些场景,如远端数据库服务器的调用,早期的服务器端编程,使用消息机制来进行事务的处理。由于过于占用CPU资源,因此,引进了事件机制,如epoll等。又由于会产生无谓的阻塞浪费线程资源,因此,引进了协程来解决这个问题。
多协程和多线程目的比较
多线程是为了压榨CPU的能力,多协程是为了压榨每个线程(每个进程默认有一个主线程)的能力。
协程的使用
1、适用于该线程没有被充分利用的场景,如果该线程已经很忙了,则没有必要引入协程了。
2、由于多协程是使用单线程模拟出来的,因此,不能使用阻塞调用,因为,这样会卡住该线程(卡住所有协程)。
3、由于多协程是使用单线程模拟出来的,所以,对于临界资源的访问不用加锁。
相关文章推荐
- 2016 Multi-University Training Contest 10
- ORB-SLAM 笔记
- html本地存储localStorage和sessionStorage存对象
- mysql 文件导入方法总结
- navicat在ubuntu下中文乱码的真正解决方法ZT
- jvm classLoader architecture :
- Remove Element
- 设计模式之模板模式
- RNN代码解读之char-RNN with TensorFlow(model.py)
- udp_server
- Linux 下 mysql添加用户并授权数据库权限
- Redis
- elastic search sql 按字段设置分词器
- HDU 4366 Successor 分块做法
- 【POJ 2396】 Budget 带上下界网络流 解题报告
- ubuntu常用软件包deb的安装与卸载
- trie树的实现和应用及测试
- 析构函数定义为虚函数场景(多态应用)
- 三国轶事--巴蜀之危 排列组合
- virtualenvwrapper