如何看网站是否被降权,西安网站维护 策划,北京手机网站开发公司,网站开发不懂英语协程设计原理与汇编实现
同步与异步
对于任何一个事情#xff0c;都可以划分为不同的步骤。所谓同步#xff0c;就先做第一个事情#xff0c;按照这件事的步骤完成这件事之后#xff0c;再去做第二件事。再去做第三件事#xff0c;以此类推。
异步就是#xff0c;可以…协程设计原理与汇编实现
同步与异步
对于任何一个事情都可以划分为不同的步骤。所谓同步就先做第一个事情按照这件事的步骤完成这件事之后再去做第二件事。再去做第三件事以此类推。
异步就是可以先开始做第一件事从第一个步骤开始但是当做到某个步骤之后需要暂停等待于是跳转到了第二件事又开始从第二件事的第一个步骤开始做当做到某个步骤后又需要暂时等待于是跳转到了第三个事件从第一个步骤开始做可能做到某个步骤后发现第一个事情暂停的步骤被唤醒了于是转过头去继续把后续的事情做完再去看第二件事等待的资源是否完成。以此类推。
所以从宏观上即从某个时间段来看同步像是分开进行的异步像是同时执行的。这一点和操作系统中的某些概念是类似的。
什么是协程
协程的核心就是以同步的方式实现异步的性能。以上面的例子讲解就是用不同的函数实现不同的步骤这样在编程看起来是同步的就是所谓的以同步的方式然后不同函数在实现步骤的时候会设定一个类似于原语的操作如果这个步骤无法立刻完成就跳转到下一个事情中去直到满足条件之后再回来继续完成该事件这个原语即是实现了异步的性能。
如何实现协程
第一种方式setjmp/longjmp
首先需要定义一个 jmp_buf类型的变量env代表此时的环境编号类似于存档点。
setjmp函数调用此函数会保存当前系统的堆栈里的数据进行存档。返回值为0代表首次进行存档。返回值为x代表的下一次回退到存档点应该走的路线。
longjmp函数调用此函数会直接回退到存档点其中函数的第二个参数x就是上面setjmp下一次要返回的值。即下一次要走的路线
举例
#includestdio.h
#includesetjmp.hjmp_buf env;int fun(int arg){printf(fun %d \n, arg);arg;longjmp(env, arg);return 0;
}int main(){int ret setjmp(env);if(ret 0){fun(ret);}else if(ret 1){fun(ret);}else if(ret 2){fun(ret);}else if(ret 3){fun(ret);}}
弊端
如果是多线程会出现不同的堆栈这样在保存的时候会出现函数未定义等情况。不建议使用 第二种方式ucontext
ucontext相比于上一个类似于让用户自己实现了上下文信息的保存而不是像setjmp一样通过调用函数让系统来保存。
定义了一个ucontext的结构体来保存上下文信息。
调用getcontextuc函数把上下文信息保存在uc结构体中并且初始化该结构体初始化的内容分配数组、确定返回、帮定函数
调用swapcontextctxctx2切换上下文。
#includeucontext.h
#includestdio.hucontext_t ctx[3], main_ctx;
int count 0;void fun1(){while (count 30){printf(1\n);swapcontext(ctx[0], ctx[1]);printf(4\n);}
}void fun2(){while (count 30){printf(2\n);swapcontext(ctx[1], ctx[2]);printf(3\n);}
}void fun3(){while (count 30){printf(3\n);swapcontext(ctx[2], ctx[0]);printf(6\n);}
}int main(){int stack1[2048] {0};int stack2[2048] {0};int stack3[2048] {0};getcontext(ctx[0]);ctx[0].uc_stack.ss_sp stack1;ctx[0].uc_stack.ss_size sizeof(stack1);ctx[0].uc_link main_ctx;makecontext(ctx[0], fun1, 0);getcontext(ctx[1]);ctx[1].uc_stack.ss_sp stack2;ctx[1].uc_stack.ss_size sizeof(stack2);ctx[1].uc_link main_ctx;makecontext(ctx[1], fun2, 0);getcontext(ctx[2]);ctx[2].uc_stack.ss_sp stack3;ctx[2].uc_stack.ss_size sizeof(stack3);ctx[2].uc_link main_ctx;makecontext(ctx[2], fun3, 0);printf(swapcontext\n);swapcontext(main_ctx, ctx[0]);printf(\n);} 我的一些理解
在正常执行主函数调用子函数的时候系统内部是会帮我们实现一些操作的会保存当前堆栈的信息然后再另外分配一个新的空间堆栈用来执行子函数当子函数执行完毕再返回调用时堆栈的状态。
那么协程其实就是要求用户自己实现了这样一个过程而不是再交给系统来做了。首先需要初始化ctx结构体就相当于完成了调用子函数的功能getcontext实现保存当前堆栈信息ctx的uc_stack实现了子函数内存的分配ctx的uc_link实现了子函数的返回地点makecontext实现了给这个子函数命名。与调用子函数不同的点在于可以在子函数内部使用swapcontext用于不同子函数的切换其中内部的堆栈会记录该子函数目前执行到了哪一个步骤继续往下执行。
从这张图其实可以看出所谓的“同步的方式实现异步的性能”手动模拟了调用子函数的过程即为“同步的方式”但是在实现子函数内部中又允许不同子函数之间切换蓝色是协程实现的核心即实现了“异步的性能”。
协程实现了类似于操作系统中处理机的时间片轮转的操作。 弊端这样如果是协程之间互相切换不可控于是需要实现一个调度器schedual
调度器实现的就是不让子函数之间相互切换而是需要切换时切到主函数中去这样可控。以下代码中main里面的while循环就是一个简单的调度器子函数中swapcontext也是跳转到主函数中去。
#includeucontext.h
#includestdio.hucontext_t ctx[3], main_ctx;
int count 0;void fun1(){while (count 30){printf(1\n);//swapcontext(ctx[0], ctx[1]);swapcontext(ctx[0], main_ctx); //跳转到主函数中去而不是子涵数间互转printf(4\n);}
}void fun2(){while (count 30){printf(2\n);//swapcontext(ctx[1], ctx[2]);swapcontext(ctx[1], main_ctx);printf(3\n);}
}void fun3(){while (count 30){printf(3\n);//swapcontext(ctx[2], ctx[0]);swapcontext(ctx[2], main_ctx);printf(6\n);}
}int main(){int stack1[2048] {0};int stack2[2048] {0};int stack3[2048] {0};getcontext(ctx[0]);ctx[0].uc_stack.ss_sp stack1;ctx[0].uc_stack.ss_size sizeof(stack1);ctx[0].uc_link main_ctx;makecontext(ctx[0], fun1, 0);getcontext(ctx[1]);ctx[1].uc_stack.ss_sp stack2;ctx[1].uc_stack.ss_size sizeof(stack2);ctx[1].uc_link main_ctx;makecontext(ctx[1], fun2, 0);getcontext(ctx[2]);ctx[2].uc_stack.ss_sp stack3;ctx[2].uc_stack.ss_size sizeof(stack3);ctx[2].uc_link main_ctx;makecontext(ctx[2], fun3, 0);printf(swapcontext\n);//简单的调度实现while(count 30){swapcontext(main_ctx, ctx[count%3]);}printf(\n);}
课程地址www.github.com/0voice