• 协程知识点总结


    思路

    先理解

    • Select / poll /epoll ? 基于事件驱动来做的 io 复用
    • Reactor ? 用epoll来封装的反应堆
    • Http 网络http协议 协议头 如何实现内部 get cgi
    • Webserver ? 做一个网络的服务端 基于reactor
    • 单线程 多线程 多进程reactor?
    • Posix api 和协议栈 tcp协议栈
    • Udp 协议 kcp

    在理解

    • 为什么会有协程?解决什么问题?同步的线程 异步的操作
    • 原语操作有哪些?同步和异步的切换 操作中最基本的单位eg createyelid resume
    • 切换 (Switch)类似于线程 进程的切换
    • 协程的运行流程
    • 协程结构体定义
    • 调度策略 类似于 进程调度 lua算法等一系列的策略 cfs
    • 调度器如何定义?
    • 协程 api 的实现,hook?
    • 多核模式
    • 如何测试?

    注意: 没有特别声明 fdsockfd连接io 指的都是 fd!!!

    为什么会有协程?解决什么问题?

    • 理解什么是同步什么是异步?
    • 理解什么是同步的编程方式异步的性能?同步的编程方式和异步的编程方式的优缺点是什么?
    • 为什么异步的编程方式避免不了多个线程共用一个 fd?导致数据乱序?
    • 适用于服务器与客户端异步处理。

    IO 同步、异步操作对比
    在这里插入图片描述

    站在服务器端

    比如 reactor 监听时间。
    同步 7.5s 连接1000个 。
    异步 1.4s 连接1000个。

    //同步
    while1{
    	epoll_wait
    		For(;;)
    	{
    		Recv();
    		Send();
    	}
    }
    
    //异步
    Push_other_thread()
    {
    	Poll(); //检测 可读可写  治标不治本 不能根除 多个线程同用一个fd
    	Recv();
    	Send();
    }
    While(1{
    	Epoll_wait()For(;;)
    	{
    		Push_other_thread();
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    分析:

    • Io 异步的问题 push_other_thread 避免不了多个线程公用一个fd的现象 尽量的去避免
    • 问题乱序读的数据不对 A再读 B 关闭。莫名其妙的就出现问题 ,如何解决线程前加poll再次检测但是治标不治本 。

    站在客户端的角度

    客户端发送之后等待服务端的结果,发送50个域名给客户端进行请求同步的没异步的处理快。
    总结不管是再客户端还是服务端 io异步的性能总是快。

    通过站在客户端与服务器端推出:
    协程就是解决了同步的性能,异步的共用现象使用同步的编程,却实现了异步的操作处理

    如何把同步的编程变成异步的呢?

    正常的流程如下:

    1. 建立连接
    2. 当客户端发送一个请求到服务端时候,send();
    3. Epoll_wait判断是否可读;
    4. 等待服务端一个回发数据recv() (不可读会阻塞)同步,再去做一系列的操作

    改成如下:

    1. 建立连接
    2. 当客户端发送一个请求到服务端时候send(),send之后切换成epoll_wait, if(不可读){ 开另外一个流程再次send },相当于异步操作 开了另外一个io口去做send ,epoll_wait判断是否可读.
    3. 等待服务端一个回发数据recv() (不可读会阻塞) 但是不影响上面,因为上面会切换另外一个请求去再次处理其他的send,再去做一系列的操作。
    4. 在send完毕后做一个切换到epoll_wait去判断fd是否可读不可读,让出回到主线程,去另外一个流程中再次send

    总结:
    先理解io操作io检测核心:遇到 io 操作就 yeid
    send() 一次 fd ----》 利用epoll_ctl()加入epoll_wait()中检测 ----》 yeid(让出)跳入到epoll_wait()检测 if(可读){ recv() , 继续epoll_wait()检测} else { resume()}

    切换(Switch)

    • yield:IO 操作 ----》 IO 检测
    • resume:IO 检测 ----》 IO 操作

    协程入口函数
    在这里插入图片描述

    
    server_reader -> nty_recv -> nty_poll_inner -> nty_schedule_sched_wait -> _nty_coroutine_yield
    
    • 1
    • 2

    协程结构体定义

    举例:以客户端与服务器连接时,客户端流程

    void client_func()
    {
        send(fd, buffer, length, 0);
        int nready = epoll_wait(epfd, ); //加一个判断把同步的流程改成异步;
        recv(fd, rbuffer, length, 0);  //阻塞住  不要的
        //....
        //parser()解析服务端返回的数据
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    协程结构体定义

    //用宏写 C++ 模板
    #define queue_node(name type) struct name{\  
        struct type *next;
        struct type *prev;
    }
    #define rbtree_node(name type) struct name{\
        char color;
        struct type *right;
        struct type *left
        struct parents;
    }
    
    struct cpi_register_set
    {
        void* eax; //寄存器上下文
        void* ebx; //寄存器上下文
        ....
    };
    
    struct coroutine
    {
    	// 类比线程 来定义协程需要的结构
        struct cpi_register_set *set; //保存cpu
        void *coroutine  e_create;// entry 协程入口函数
        void *arg; // 参数
        void *reval; //返回值 不做计算的话没什么意义
    
    	// 函数调用如何调用?  利用栈
        void *stack_addr; //指向栈空间的首地址,在堆上开辟一个空间当做栈
        size_t stack_size;
    
    	// 协程的数量很多 要用什么结构来储存   
    	// 从调度器的角度来看  栈的格式不符合(会导致最底部的协程调用不到)stack_coroutine *next;
    	// 队列的格式来用  
        
        queue_node(ready_name,coroutine) *ready;
        rbtree_node(wait_name,coroutine) *wait;
        rbtree_node(sleep_name,coroutine) *sleep;
    }co;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    协程的创建类比线程的创建

    pthread_create(thid, NULL, entry_cb, arg) { }  //线程id, 线程属性(堆栈大小的参数),线程入口函数,参数
    只做了两件事情:
    1. 创建一个线程实体:task_struct *task;
    2. 加入就绪队列 :enqueue_ready;
    
    • 1
    • 2
    • 3
    • 4
    //协程创建
    oroutine_create(entry_cb,arg);
    
    coroutine_join(coid,&reval)  //获取子线程返回的值
    {
        co = search(coid)
        if(co->reval ==NULL){
            wait(); //单线程 用条件等待
        }
        return co->reval;    
    }
    
    exec(co){
        //启动了协程函数 取得返回值
        co->reval = co->func(co->arg)
        //存到了协程的结构体里
        signal();//  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    调度策略

    调度器

    //调度器
    struct scheduler{
        struct scheduler_ops *ops;
        //当前运行的协程  当调度器让出的时候、
        struct coroutine *cur;  //当前哪个协程
        int epfd;  //
    
        queue_node *ready_set;
        rbtree() *wait_set;
        rbtree() *sleep_set;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    协程 api 的实现,hook?

    • socketbindlistenconnectacceptsendrecv 同步改为异步
    • 利用 hook 函数
    //异步api
    void my_accept(){
        int ret=Poll(fd);
        if(ret>0){
            //就绪后 调用真正的accept
            accept();
        }
        else{
            //吧fd加入到epoll 后   “让出”   epoll管理
            epoll_ctl(epfd);   //time out 值设成0 立刻返回 做成异步的操作
            yield();			 //调度器用
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    hook 是把代码段中的改变 在进程开启阶段会调用inin_hook(),把函数内部 把系统调用对应的 指定函数 (send/recv)改成(send_f/recv_f)。
    在应用下 用到了send(普通函数了)或者recv(普通函数了) 会自动的调用send_f/recv_f替换。

    Yield之后如何执行 yield和resume如何实现的呢?让出到对应的resume下 resume到对应的yield下。

    充电站
    推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

  • 相关阅读:
    会议OA系统
    【基操】ArcGIS三种筛选提取要素的方法
    Kafka - 03 Kafka单机环境和伪集群环境的搭建
    科罗廖夫年谱
    【存储RAID】存储RAID常见模式及其特点
    【Flutter】Flutter 中 sqflite 的基本使用
    STM32CubeMX 学习(5)输入捕获实验
    论文解读(SentiX)《SentiX: A Sentiment-Aware Pre-Trained Model for Cross-Domain Sentiment Analysis》
    centos 7 yum install -y nagios
    支持在线状态检查的仪表板miniboard
  • 原文地址:https://blog.csdn.net/weixin_53492721/article/details/126076946