协程学习

来源:互联网 发布:java多态性的应用 编辑:程序博客网 时间:2024/05/14 03:32

感谢同事的分享,对协程有了初步的认识。

一.基本概念

协程(Coroutine, 又被称呼为“纤程”、“微线程”)顾名思义就是“协作的例程”(Co-operative routines)。跟具有操作系统概念的线程不一样,协程是在用户空间利用程序语言的语法语义就能实现逻辑上类似多任务的编程技巧

简单的说,协程就是类似函数一样的程序组件,程序员可以在一个线程里面轻松创建数十万个协程,就像数十万次函数调用一样。然而,不同的是,函数只有调用入口一个起始点,返回之后函数就结束了;协程的入口可以是起始点,也可以接着从上一个返回点继续执行,即可重入。协程与协程直接可以平级的转移执行权(yield),而普通函数则必须遵守调用与被调用的非平级关系。

二.协程示例

典型的消费者-生产者模式中,传统的解决方案是多线程+互斥锁,即一个线程写数据,一个线程读数据,用互斥锁保护临界区,如下图所示。


缺点:1 互斥锁的问题:阻塞挂起,优先级反转,死锁。 2 创建线程开销昂贵。

         如果改用协程,解决方案如下:生产者协程生产数据后,直接通过yield跳转到消费者协程开始执行,待消费者消费数据完毕后,切换回生产者继续生产数据


伪代码如下:yield是指,当前协程让出执行权,使得其他协程获得执行权并运行。

   producer_coroutine() {

    loop

       while queue is not full

       create some new items

       add the items to queue

       yield to consumer_coroutine

    }

 

    consumer_coroutine(){

    loop

        while queue is not empty

       remove some items from queue

       use the items

       yield to producer_coroutine

    }

    main(){

        create producer_coroutine

        create consumer_coroutine

        resume producer_coroutine

    }

协程的优点

1. 代码可读性强,整个过程可以在一个线程内由用户态自行调度完成;

2. 无需内核调度子系统参与切换上下文。

协程的缺点:

相比多线程的办法,损失了使用多核的能力

三.协程,进程,线程区别

进程:一个进程就是一个正在运行的程序实例,包括程序计数器,寄存器和变量当前值。每个进程都有它虚拟的cpu和虚拟内存地址空间,资源句柄等,不同进程彼此隔离。Linux内核中每个进程都使用一个名为task_struct的结构来描述。这个结构包含了进程的权限,pid,ppid, 子进程链表,进程组关系,调度优先级,运行状态,namespace, 安装的信号处理程序,虚拟内存等重要信息。创建进程的方法:fork()。

线程:进程并不是操作系统的唯一程序运行方式,另一种形式是线程。UNIX以及类UNIX系统中,线程是以轻量级进程的形式实现。在linux内核中,每个线程也拥有独立的task_struct结构,因此,每个线程也拥有自己独立的pid。一个进程中可以包含多个同时运行的线程,这些线程共享了同一个虚拟内存地址空间和系统资源。Linux下线程的创建:

 1 直接使用系统调用:clone(),fork()也是调用clone()。

 2 创建POSIX线程:pthread_create(),实际也是调用的clone()。

协程:与进程、线程最大的区别在于:协程并不是操作系统语义下的概念。操作系统并不参与协程的控制和调度,这一切完全在用户态进程内完成。切换协程并不需要陷入内核态,不需要操作系统的调度子系统参与,避免了进程线程间的上下文切换,无需更新进程虚拟内存页表和寄存器信息。相比之下,协程的调度切换可以完全在用户态进行,无需操作系统干涉,代价低廉。因此,无论是创建协程,还是切换协程,代价都远远小于创建进程/线程和切换进程/线程。

四.协程的实现

一些高级编程语言已经在语言层次上实现了协程,比如go, lua;另外一些语言通过第三方库实现了协程,比如python的gevent, nodejs的fiber。C/C++目前只能通过第三方库来实现协程,例如boost coroutine, libtask(go语言的启发者), libco(来自腾讯微信部门)。libco的源代码在github上可以找到。

协程实现的技术关键在于用户态程序员需要在进程内自行处理协程的调度与切换。C语言的库函数setjmp(3)和longjmp(3)提供了一种函数间跳转机制,其中setjmp(3)实现了保存当前函数的堆栈帧信息到jmp_buf结构中,longjmp(3)则负责通过jmp_buf保存的信息来修改esp和eip以及寄存器信息来实现跳转。goto只能在函数内跳转。

目前有部分协程库是基于setjmp(3)和longjmp(3)实现的, 而glibc对这两个函数的实现则直接采用了gcc内嵌汇编的方式。同时,也有部分协程库直接使用gcc内嵌汇编来完成协程间的上下文切换,主要是利用函数栈的方式实现。

五. libco的实现原理

libco协程框架Hook(劫持)了socket接口,如connect、read、write等。用户在使用libco协程库时,实际调用的是被hook的系统调用。

在Hook的网络接口函数中,做的事情是:

1. 把本次网络请求注册为异步事件,当前的协程让出CPU占用。
2. 在网络事件发生或者超时的时候,恢复此协程执行。


有了这个hook函数,就可以在不修改线上业务逻辑代码的情况下,完成异步化改造

为了防止同一个变量被多个协程同时调用,所以在使用全局变量时,要特别注意,可以创建变量池,或者使用局部全量,保证变量不会被多个协程同时使用

协程适用于网络I/O的情况,不适用于磁盘I/O,CPU密集型等应用。


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 政府采购2次废标怎么办 车卖给别人车牌怎么办 医用耗材中标后怎么办 国六标准国五车怎么办 年审标志丢了怎么办 大专不过统招线怎么办 雅安停天然气了怎么办 调档函过期了怎么办 临时工想涨工资怎么办 辞职了职称公需课怎么办 一师一优课有账号忘记密码怎么办? 长沙转户口档案怎么办 二战迁户口档案怎么办 本人户口页丢失怎么办 户口迁出原籍档案怎么办 户口本丢了一页怎么办 户口本少了一页怎么办 户口页丢了怎么办 公司要社保卡怎么办 公务员面试缺考怎么办 word文档未标签怎么办 暂住证到期了怎么办t 考驾照没暂住证怎么办 南京暂住证过期了怎么办 南京桥北暂住证怎么办 冠状沟红痒一年多了怎么办 介仓身上痒怎么办 专升本考试怎么办 专升本毕业论文怎么办 手机扫码模糊怎么办 iphone相册闪退怎么办 快件签收扫描失败怎么办 创业迷茫的时候怎么办 月经期间腰酸痛怎么办 被重庆微跑骗了怎么办 遴选到中央房子怎么办 转了户口社保怎么办 政府咨询电话打不通怎么办 公务员准考证丢了怎么办 行测老打40多分怎么办 网上没有报名的怎么办?