-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
8 lines (8 loc) · 5.22 KB
/
Copy pathsearch.xml
File metadata and controls
8 lines (8 loc) · 5.22 KB
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[协程及其实现]]></title>
<url>%2F2018%2F03%2F11%2Fcoroutine%2F</url>
<content type="text"><![CDATA[什么是协程协程:在单线程中实现多线程的编程模式。协程没有多线程的上下文切换消耗,适合IO密集型程序。可以用协程+多进程利用多核。我们用下面一段伪代码来解释什么是协程:1234567891011121314151617#include <stdio.h>## 标题 ##void ThreadA() { printf("A0\n"); printf("A1\n");}void ThreadB() { printf("B0\n"); printf("B1\n");}int main() { while (true) { ThreadA(); ThreadB(); } return 0;} 显然,上面的输出如下: A0A1B0B1…… 如果我们实现下面的输出,也就实现了协程: A0B0A1B1…… 对于这种输出,ThreadA和ThreadB就像是两个独立的线程在运行。那么,如何实现这种输出即如何实现协程呢? 利用语法技巧实现协程对于c/c++,我们可以利用goto和静态变量来实现,代码如下:12345678910111213141516171819202122232425262728293031323334353637#include <stdio.h>void ThreadA(void) { static int state = 0; switch (state) { case 0: goto LABEL0; case 1: goto LABEL1; } LABEL0: state = 1; printf("A0\n"); return; LABEL1: state = 0; printf("A1\n");}void ThreadB(void) { static int state = 0; switch (state) { case 0: goto LABEL0; case 1: goto LABEL1; } LABEL0: state = 1; printf("B0\n"); return; LABEL1: state = 0; printf("B1\n");}int main() { while (true) { ThreadA(); ThreadB(); } return 0;} 上面的输出如下: A0B0A1B1…… state静态变量保存了函数上次调用的位置(可以理解为协程的“堆栈”),再利用c/c++的goto语言特性,我们实现了协程。 利用语法技巧实现的协程库http://dunkels.com/adam/pt 这是一个开源C协程库,有效代码不足100行,原理和上面讲的类似。 利用函数调用栈实现的协程库——libcolibo是利用函数调用栈特点实现的开源协程库。我们先来看下函数调用栈结构:以下面函数调用举例:1234567void Func(int x, int y, int z){ int a, b, c; return;}Func(10, 5, 2); Func的汇编代码如下:12345678910```Func: push %ebp # 将ebp压栈(保存函数调用者的栈基址,后面利用ebp来定位变量的地址) mov %esp, %ebp # 将ebp指向栈顶esp(设置当前函数的栈基址) sub esp, 12 # 分配栈空间 sizeof(a) + sizeof(b) + sizeof(c) ... # x = [ebp + 8], y = [ebp + 12], z = [ebp + 16] # a = [ebp - 4] = [esp + 8], b = [ebp - 8] = [esp + 4], c = [ebp - 12] = [esp] mov esp, ebp # Func收尾工作,esp和ebp开始指向函数调用者的栈帧 pop ebp ret 12 # sizeof(x) + sizeof(y) + sizeof(z),返回到调用Func函数处继续执行 函数调用栈如下:12345678910: : | 2 | [ebp + 16] (3rd function argument)| 5 | [ebp + 12] (2nd argument)| 10 | [ebp + 8] (1st argument)| RA | [ebp + 4] (return address) # 这里是协程关键,我们可以改变RA的值,从而让函数调用完后返回到协程指定的地址| FP | [ebp] (old ebp value)| | [ebp - 4] (1st local variable): :: :| | [ebp - X] (esp - the current stack pointer. The use of push / pop is valid now) 函数Func执行完后会返回调用处继续执行,libco的原理就是改变返回的地址RA,使函数执行完后跳转到指定的协程函数处运行。这部分是通过libco的coctx_swap函数实现的,其定义如下:extern void coctx_swap( coctx_t ,coctx_t ) asm(“coctx_swap”);coctx_swap是用汇编写的,不是标准的函数调用栈汇编,因为改变了函数调用的返回地址,其汇编实现如下: 先来看相关数据结构:123456789#define ESP 0#define EIP 1#define EAX 2#define ECX 3...struct coctx_t{ void *regs[ 8 ];}; 我们来分析coctx_swap(ctx1, ctx2),首先将当前的上下文环境保存到ctx1结构中(ctx1,ctx2中保存了要执行的函数):1234567891011leal 4(%esp), %eax # eax = old_esp + 4 movl 4(%esp), %esp # 将esp的值设为ctx1的地址(ctx1就是coctx_swap第一个参数的地址)leal 32(%esp), %esp # esp = (char*)&ctx1 + 32 pushl %eax # ctx1->regs[EAX] = %eax pushl %ebp # ctx1->regs[EBP] = %ebppushl %esi # ctx1->regs[ESI] = %esipushl %edi # ctx1->regs[EDI] = %edipushl %edx # ctx1->regs[EDX] = %edxpushl %ecx # ctx1->regs[ECX] = %ecxpushl %ebx # ctx1->regs[EBX] = %ebxpushl -4(%eax) # ctx1->regs[EIP] = RA (将返回地址保存在ctx1的EIP中) 然后该函数将ctx2中保存的上下文恢复到寄存器中,并跳转到其函数地址处运行:123456789101112movl 4(%eax), %esp # 将esp的值设为ctx2的地址popl %eax # %eax = ctx1->regs[EIP],即&pfnpopl %ebx # %ebx = ctx1->regs[EBP]popl %ecx # %ecx = ctx1->regs[ECX]popl %edx # %edx = ctx1->regs[EDX]popl %edi # %edi = ctx1->regs[EDI]popl %esi # %esi = ctx1->regs[ESI]popl %ebp # %ebp = ctx1->regs[EBP]popl %esp # %esp = ctx1->regs[ESP]pushl %eax # RA = %eax = &pfn,此时esp已经指向了新的espxorl %eax, %eax # reset eaxret 通过coctx_swap(ctx1, ctx2)就实现了跳转到ctx2中的函数上去执行。libco的使用见项目https://github.com/Tencent/libco 这里不再赘述。]]></content>
</entry>
</search>