协程源码剖析(二)

简介: 协程源码剖析(二)

接上一节协程源码剖析(一)

接下来讨论协程对系统调用的覆盖:hook

本文列举部分源码,协程全部源码https://github.com/wuj1nquan/bitco每一个函数都有详细注释

协程为了逻辑清晰,便于维护,使用同步编程方式

因此使用posix提供的系统调用recv、send等都会在io未就绪时阻塞

所以协程对这些api进行了异步改造

先来看一个简单的例子:

hook系统调用
ssize_t read(int fd, void *buf, size_t count) { // 上下文切换实现非阻塞read
  struct pollfd fds;
  fds.fd = fd;
  fds.events = POLLIN | POLLERR | POLLHUP;
  poll_inner(&fds, 1, 1); // 将当前fd交由epoll管理并让出cpu,避免read_f阻塞,等待fd就绪后由调度器返回到这里继续执行read_f
  int ret = read_f(fd, buf, count);// 这里是真正的系统调用read
  if (ret < 0) {
    
    if (errno == ECONNRESET) return -1;
  }
  return ret;
}

这里对系统调用进行hook后,重新定义了一个read函数,借助poll_inner函数实现了异步read

poll_inner函数
static int poll_inner(struct pollfd *fds, nfds_t nfds, int timeout) {
  if (timeout == 0)
  {
    return poll(fds, nfds, timeout);
  }
  if (timeout < 0)
  {
    timeout = INT_MAX;
  }
  schedule *sched = coroutine_get_sched(); // 获取当前正在执行的协程,也就是调用poll_inner所在函数(例如read)所在的协程
  if (sched == NULL) {
    printf("scheduler not exit!\n");
    return -1;
  }
  
  coroutine *co = sched->curr_thread; // 获取协程对象
  
  int i = 0;
  for (i = 0;i < nfds;i ++) { // 将poll监视的文件描述符和相应的事件转换为 epoll 事件
  
    struct epoll_event ev;
    ev.events = pollevent_2epoll(fds[i].events);
    ev.data.fd = fds[i].fd;
    epoll_ctl(sched->poller_fd, EPOLL_CTL_ADD, fds[i].fd, &ev);
    co->events = fds[i].events;
    schedule_sched_wait(co, fds[i].fd, fds[i].events, timeout); // 让当前协程等待事件
  }
  coroutine_yield(co); // 当前协程将控制权交给调度器,等待事件发生
  for (i = 0;i < nfds;i ++) { // 处理事件完成后从 epoll 实例中删除已经监视的文件描述符,并清除相应的等待状态
  
    struct epoll_event ev;
    ev.events = pollevent_2epoll(fds[i].events);
    ev.data.fd = fds[i].fd;
    epoll_ctl(sched->poller_fd, EPOLL_CTL_DEL, fds[i].fd, &ev);
    schedule_desched_wait(fds[i].fd); // 处理完成后将该协程从调度器的等待红黑树中删除
  }
  return nfds; // 返回监视文件描述符数量
}

poll_inner函数将当前进行read操作的fd加入epoll管理,随后,其所在协程让出控制权给调度器,下次恢复时,fd已经就绪,实现了异步read

如此覆盖系统调用,这就是各协程之间通过系统调用借助调度器切换的底层原理

由于篇幅,只能列举部分源码进行说明,协程全部源码
协程源码
每一个函数都有详细注释,学习首选

下一节继续讲解协程原语操作resume、yield

推荐学习https://xxetb.xetslk.com/s/p5Ibb

目录
相关文章
|
10月前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
10月前
|
网络协议 NoSQL API
协程知识点总结
协程知识点总结
|
9月前
|
监控 程序员 调度
协程实现单线程并发(入门)
协程实现单线程并发(入门)
93 1
|
9月前
|
调度
协程源码剖析(一)
协程源码剖析(一)
50 0
|
10月前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
279 0
|
10月前
|
调度
解释一下为什么协程比线程更轻量级。
解释一下为什么协程比线程更轻量级。
368 1
|
10月前
|
C++
C/C++协程学习笔记
C/C++协程学习笔记
|
10月前
|
存储 前端开发 rax
协程学习笔记 NtyCo/libgo
协程学习笔记 NtyCo/libgo
121 0
|
10月前
|
存储 前端开发 rax
协程学习笔记
协程学习笔记
66 0
终于明白:有了线程,为什么还要有协程?
其实,在早期计算机并没有包含操作系统,这个时候,这个计算机只跑一个程序,这个程序独享计算机的所有资源,这个时候不存在什么并发问题,但是对计算机的资源来说,确实是一种浪费。早期编程都是基于单进程来进行,随着计算机技术的发展,于是,操作系统出现了,操作系统改变了这种现状,让计算机可以运行多个程序,并且不同的程序占用独立的计算机资源,如内存,CPU等。