彻底理解什么是同步和异步!( 三 )


另一种是就是回调 , 也就是我们常说的callback , 关于回调我们将在下一篇文章中重点讲解 , 本篇会有简短的讨论 。
接下来我们用一个具体的例子讲解一下同步调用与异步调用 。
 
同步 VS 异步我们以常见的Web服务来举例说明这一问题 。
一般来说Web Server接收到用户请求后会有一些典型的处理逻辑 , 最常见的就是数据库查询(当然 , 你也可以把这里的数据库查询换成其它I/O操作 , 比如磁盘读取、网络通信等) , 在这里我们假定处理一次用户请求需要经过步骤A、B、C , 然后读取数据库 , 数据库读取完成后需要经过步骤D、E、F , 就像这样:
# 处理一次用户请求需要经过的步骤:A;B;C;数据库读取;D;E;F;其中步骤A、B、C和D、E、F不需要任何I/O , 也就是说这六个步骤不需要读取文件、网络通信等 , 涉及到I/O操作的只有数据库查询这一步 。
一般来说这样的Web Server有两个典型的线程:主线程和数据库处理线程 , 注意 , 这讨论的只是典型的场景 , 具体业务实际上可会有差别 , 但这并不影响我们用两个线程来说明问题 。
首先我们来看下最简单的实现方式 , 也就是同步 。
这种方式最为自然也最为容易理解:
// 主线程mAIn_thread() {A;B;C;发送数据库查询请求;D;E;F;}// 数据库线程DataBase_thread() {while(1) {处理数据库读取请求;返回结果;}}这就是最为典型的同步方法 , 主线程在发出数据库查询请求后就会被阻塞而暂停运行 , 直到数据库查询完毕后面的D、E、F才可以继续运行 , 就像这样:

彻底理解什么是同步和异步!

文章插图
图片
从图中我们可以看到 , 主线程中会有“空隙” , 这个空隙就是主线程的“休闲时光” , 主线程在这段休闲时光中需要等待数据库查询完成才能继续后续处理流程 。
在这里主线程就好比监工的老板 , 数据库线程就好比苦逼搬砖的程序员 , 在搬完砖前老板什么都不做只是紧紧的盯着你 , 等你搬完砖后才去忙其它事情 。
显然 , 高效的程序员是不能容忍主线程偷懒的 。
是时候祭出大杀器了 , 这就是异步 。
在异步这种实现方案下主线程根本不去等待数据库是否查询完成 , 而是发送完数据库读写请求后直接处理下一个请求 。
有的同学可能会有疑问 , 一个请求需要经过A、B、C、数据库查询、D、E、F这七个步骤 , 如果主线程在完成A、B、C、数据库查询后直接进行处理接下来的请求 , 那么上一个请求中剩下的D、E、F几个步骤怎么办呢?
如果大家还没有忘记上一小节内容的话应该知道 , 这有两种情况 , 我们来分别讨论 。
 
1 , 主线程不关心数据库操作结果在这种情况下 , 主线程根本就不关心数据库是否查询完毕 , 数据库查询完毕后自行处理接下来的D、E、F三个步骤 , 就像这样:
彻底理解什么是同步和异步!

文章插图
图片
看到了吧 , 接下来重点来了哦 。
我们说过一个请求需要经过七个步骤 , 其中前三个是在主线程中完成的 , 后四个是在数据库线程中完成的 , 那么数据库线程是怎么知道查完数据库后要处理D、E、F这几个步骤呢?
这时 , 我们的另一个主角回调函数就开始登场啦 。
没错 , 回调函数就是用来解决这一问题的 。
我们可以将处理D、E、F这几个步骤封装到一个函数中 , 假定将该函数命名为handle_DEF_after_DB_query:
void handle_DEF_after_DB_query () {D;E;F;}这样主线程在发送数据库查询请求的同时将该函数一并当做参数传递过去:
DB_query(request, handle_DEF_after_DB_query);数据库线程处理完后直接调用handle_DEF_after_DB_query就可以了 , 这就是回调函数的作用 。
也有的同学可能会有疑问 , 为什么这个函数要传递给数据库线程而不是数据库线程自己定义自己调用呢?
因为从软件组织结构上讲 , 这不是数据库线程该做的工作 。
数据库线程需要做的仅仅就是查询数据库、然后调用一个处理函数 , 至于这个处理函数做了些什么数据库线程根本就不关心 , 也不应该关心 。


推荐阅读