从小白到高手,你须要理解同步与异步

2021年01月14日 阅读数:6
这篇文章主要向大家介绍从小白到高手,你须要理解同步与异步,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

在这篇文章中咱们来讨论一下到底什么是同步,什么是异步,以及在编程中这两个概念到底意味着什么,这些是进一步掌握高性能、高并发技术的基础,所以很是关键。程序员

相信不少同窗遇到同步异步这两个词的时候大脑瞬间就像红绿灯失灵的十字路口同样陷入一片懵逼的状态:数据库

mengbi

是的,这两个看上去很像实际上也很像的词汇给博主形成过很大的困扰,这两个词背后所表明的含义究竟是什么呢?编程

咱们先从工做场景讲起。网络

苦逼程序员

假设如今老板分配给了你一个很紧急而且很重要的任务,让你下班前必须写完(万恶的资本主义)。为了督促进度,老板搬了个椅子坐在一边盯着你写代码。并发

你内心确定已经骂上了“WTF,你有这么闲吗?盯着老子,你就不能去干点其余事情吗?”异步

老板仿佛接收到了你的脑电波同样:“我就在这等着,你写完前我哪也不去,厕所也不去”async

1600911423466

这个例子中老板交给你任务后就一直等待什么都不作直到你写完,这个场景就是所谓的同步。函数

次日,老板又交给了你一项任务。高并发

不过此次就没那么着急啦,此次老板轻描淡写“小伙子能够啊,不错不错,你再努力干一年,明年我就财务自由了,今天的这个任务不着急,你写完告诉我一声就行”。性能

此次老板没有盯着你写代码而是转身刷视频去了,你写完后简单的和老板报告了一声“我写完了”。

1600911037338

这个例子老板交代完任务就去忙其它事情,你完成任务后简单的告诉老板任务完成,这就是所谓的异步。

值得注意的是,在异步这种场景下重点是在你写代码的同时老板在本身刷剧,这两件事在同时进行所以这就是为何通常来讲异步比同步高效的本质所在,无论同步异步应用在什么场景下。

所以,咱们能够看到同步这个词每每和任务的“依赖”、“关联”、“等待”等关键词相关,而异步每每和任务的“不依赖”,“无关联”,“无需等待”,“同时发生”等关键词相关。

By the way,若是遇到一个在身后盯着你写代码的老板,三十六计走为上策。

打电话与发邮件

做为一名苦逼的程序员是不能只顾埋头搬砖的,平时工做中的沟通免除不了,其中一种高效的沟通方式是吵架。。。啊不,是电话。

email

一般打电话时都是一我的在说另外一我的听,一我的在说的时候另外一我的等待,等另外一我的说完后再接着说,所以在这个场景中你能够看到,“依赖”、“关联”、“等待”这些关键词出现了,所以打电话这种沟通方式就是所谓的同步。

1600923556187

另外一种码农经常使用的沟通方式是邮件。

邮件是另外一种必不可少沟通方式,由于没有人傻等着你写邮件什么都不作,所以你能够慢慢悠悠的写,当你在写邮件时收件人能够去作一些像摸摸鱼啊、上个厕所、和同时抱怨一下为何十一假期不放两周之类有意义的事情。

同时当你写完邮件发出去后也不须要干巴巴的等着对方什么都不作,你也能够作一些像摸鱼之类这样有意义的事情。

1600923768618

在这里,你写邮件别人摸鱼,这两件事又在同时进行,收件人和发件人都不须要相互等待,发件人写完邮件的时候简单的点个发送就能够了,收件人收到后就能够阅读啦,收件人和发件人不须要相互依赖、不须要相互等待。

你看,在这个场景下“不依赖”,“无关联”,“无需等待”这些关键词就出现了,所以邮件这种沟通方式就是异步的。

同步调用

如今终于回到编程的主题啦。

既然如今咱们已经理解了同步与异步在各类场景下的意义(I hope so),那么对于程序员来讲该怎样理解同步与异步呢?

咱们先说同步调用,这是程序员最熟悉的场景。

通常的函数调用都是同步的,就像这样:

funcA() {
    // 等待函数funcB执行完成
    funcB();
    
    // 继续接下来的流程
}

funcA调用funcB,那么在funcB执行完前,funcA中的后续代码都不会被执行,也就是说funcA必须等待funcB执行完成,就像这样:

1600925448485

从上图中咱们能够看到,在funcB运行期间funcA什么都作不了,这就是典型的同步。

注意,通常来讲,像这种同步调用,funcA和funcB是运行在同一个线程中的,这是最为常见的状况。

但值得注意的是,即便运行在两个不能线程中的函数也能够进行同步调用,像咱们进行IO操做时实际上底层是经过系统调用(关于系统调用请参考《程序员应如何理解系统调用》)的方式向操做系统发出请求的,好比磁盘文件读取:

read(file, buf);

这就是咱们在《读取文件时,程序经历了什么》中描述的阻塞式I/O,在read函数返回前程序是没法继续向前推动的

read(file, buf);
// 程序暂停运行,
// 等待文件读取完成后继续运行

如图所示:

1600925867319

只有当read函数返回后程序才能够被继续执行。

固然,这也是同步调用,可是和上面的同步调用不一样的是,函数和被调函数运行在不一样的线程中。

所以咱们能够得出结论,同步调用和函数与被调函数是否运行在同一个线程是没有关系的

在这里咱们还要再次强调,同步方式下函数和被调函数没法同时进行。

同步编程对程序员来讲是最天然最容易理解的。

但容易理解的代价就是在一些场景下,注意,是在某些场景不是全部场景哦,同步并非高效的,由于任务没有办法同时进行。

接下来咱们看异步调用。

异步调用

有同步调用就有异步调用。

关于重要的异步调用,关注公众号“码农的荒岛求生”并回复“async”你就知道了。

同步 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才能够继续运行,就像这样:

1600994106960

从图中咱们能够看到,主线程中会有“空隙”,这个空隙就是主线程的“休闲时光”,主线程在这段休闲时光中须要等待数据库查询完成才能继续后续处理流程。

在这里主线程就比如监工的老板,数据库线程就比如苦逼搬砖的程序员,在搬完砖前老板什么都不作只是牢牢的盯着你,等你搬完砖后才去忙其它事情。

显然,高效的程序员是不能容忍主线程偷懒的。

是时候祭出大杀器了,这是什么大杀器呢,关于这个问题的答案关注公众号“码农的荒岛求生”并回复“回调”二字你就知道答案了。

总结

在这篇文章中咱们从各类场景分析了同步与异步这两个概念,可是无论在什么场景下,同步每每意味着双方要相互等待、相互依赖,而异步意味着双方相互独立、各行其是。但愿本篇能对你们理解这两个重要的概念有所帮助。