最近在学习协程的时候产生了好多疑惑…知识点产生了混乱…下面总结一下。
一、关于同步异步?回调函数(同步回调,异步回调)?
(这里的 同步异步 是不分 并发和并行 的!!!讲的是异步编程!)
先看这两篇文章!!!文章1 文章2
看完再看这篇:文章3
总结一下就是:
首先,我们要先明确这里的异步并不是指异步IO,是指异步编程。
两个是不同的东西,但是思想是一样的。(当遇到阻塞时,不等待)
(再者明确:IO操作并不是由CPU管的,是由DMA管的(DMA其实可以看成一个专门处理IO操作的小型CPU))
我们看看这两者的区别:
异步IO:
个人认为是对于单线程说的,当线程调用IO系统调用时,不等待(包含两方面:不阻塞,也不轮询检查),而是继续执行,等到IO操作完发送信号给线程,然后线程直接用数据就行了。(全程CPU不等待IO操作)
异步编程:
首先看看同步编程:比如函数fun1调用fun2,则fun1在调用点停止,等fun2执行完再继续执行fun1。(可读性强)
异步编程:函数fun1调用fun2,则fun1不会停止,而是继续执行,然后有一个线程去执行fun2(是并发操作),当fun2执行完之后告知fun1就行(通过信号量或回调函数)
所以多线程是异步编程的实现方式之一。
(由于要回调函数,所以写起来很麻烦,可读性差)
二、协程是异步的吗?(这里的 同步异步 是不分 并发和并行 的!!!讲的是异步编程!)
答案:是的!
我个人认为,libco协程实现异步是通过:协程切换 + epoll + 回调函数 实现的
下面以libco协程库为例,我们分析一下。
Libco通过hook系统调用,使得我们能够以同步的编程方式实现异步。
首先,系统调用一般是等待的,比如connect(), read(), write(), recv(), send(), poll()等;
(这里我用等待不用阻塞是为了区别阻塞和非阻塞的轮询,单线程系统调用等待是同步的)
比如单线程调用read(),那是不是线程就得一直等待(包括阻塞和轮询)fd是否有数据可以读,对吧。
然后,对于libco协程库,他首先是把系统调用给hook了一下,相当于是重载了一下系统调用,然后对于用户来说,我们可以通过hook后的系统调用实现同步或异步(两种都可以选择,具体看参数的传入)。
然后看源码!看源码!看源码!
我们通过read()系统调用的源码来分析一下异步:
每次进行这些调用时,会先观察是否启用了 hook,然后观察是否 fd 设置了 O_NONBLOCK,然后再观察是否设置了 timeout == 0,如果不满足则直接调用g_sys_read_func(也就是原本的系统调用read),否则满足的话会调用poll()函数。(注意里面有一个 pf,在会面会用到,加入epoll)
下面我们看看poll干啥了:
可以看到poll也被hook了,同样前面也是先判断同步调用g_sys_poll_func()条件,g_sys_poll_func就是hook之前的poll函数;
重要的是607,609行的co_poll_inner()函数(里面将协程挂起了!)
这里注意有个回调函数g_sys_poll_func();
接着我们看co_poll_inner()函数:代码太长我就贴个链接了,ctrl+f 找一下
前面也是一些判断,调用poll传进来的那个回调函数;
其中分为3个部分:
- struct change:把poll的结构改为epoll的结构。
- add epoll:把改变后的结构加入epoll。
- add timeout:把这个事件加入超时链表。
接着在999行 将协程挂起了!将协程挂起了!
然后通过底层co_eventloop的事件循环检测,检测到了事件就绪则调用回调函数!
这就是异步编程的体现!
我们在遇到IO的时候并没有等待数据的到来,而且切换协程了。
这里我们回顾一下流程:
当前协程调用read系统调用时,通过一系列判断排除同步系统调用后,来到了co_poll_inner()函数里面添加文件描述符到epoll的阶段;
(这里我们注意到代码949行中有个回调函数OnPollProcessEvent(),它的作用是epoll中检测到事件超时或者事件就绪的时候执行的一个回调的,其实也就是执行了co_resume,切回当前协程去处理IO操作)
然后将协程挂起。
(也就是,对于协程来说,当执行协程遇到IO的时候,我们可以将协程挂起,然后切换去其他协程,当协程IO完成后,会在epoll触发就绪事件,然后epoll_wait可以检测到,通过回调函数,唤醒协程。)
在这一整个过程中协程是没有对IO操作的等待的,而是切换到其他协程上去了,仔细想想是不是就是多线程编程中的线程调度?对吧。所以我认为这就是协程异步编程的体现!
libco协程库里面有大量的回调函数…所以说libco协程库真的很妙,把异步编程都封装起来了,对于使用者来说我们只需要通过正常的同步系统调用就能实现异步编程。
结束
以上就是个人对协程和异步的理解。有什么问题或者讲的不对的欢迎留言讨论!
好了好了,今天又搞了一天…不过也体验到了看源码学习的快乐(痛苦)
对于协程的学习就暂时到这了…
我是一个大三找暑期实习的fw鼠鼠,今天又是0面的一天…加油吧!
感谢下面文章对我学习的帮助:
从 Protothreads 和 libco 看 C/C++ 实现的协程库