编程中同步、异步、串行、并行、并发的概念

首先 区别 同步 与 生活中 同步进行的概念

以下内容来自:**作者:张新潮
链接:https://www.zhihu.com/question/29634196/answer/1386224926
来源:知乎著作权归作者所有。
可以借鉴来理解编程中的同步与生活中同步进行的区别

你说的是synchronization?这个有翻译的问题,当然也有你对synchronization概念理解的问题。很多人觉得synchronization指的就是互斥,操作层面这么说其实也没什么问题,毕竟,但凡被标记成synchronized的代码段,同一时间只有一个线程可以被准入。不过互斥并不是synchronization的目的,只是达到synchronization的手段。如果你回想一下很多动作片或者科幻片,譬如谍中谍这种,在小组队员分散开各自去完成任务之前,总是有一个对表的桥段(这个对表在英文里的用词就是synchronization)。那么对表是为了做什么?你的表为什么要和你队友的高度同步?是因为你们要在同一时间做同一件事情么?或者还是因为你们不能在同一时间做同一件事情?其实都不是,你们对表的目的很简单:这样你们每一个个体在独立执行任务的时候,可以在不直接通讯的情况下,确定的知道在固定的时间你的队友是什么状态。或者说精确的知道一个该被其他人完成的任务是否已经被完成。比如当你决定纵深一越跳下风井的时候,你需要确定的知道你的队友已经把风扇关了,或者你决定直面敌人关卡的时候,你确定你的队友已经把对方的系统黑了。这是同步的真谛,或者说同步的目的:被同步的独立个体可以在某些确定的契机下看到相同的,一致的数据。而在计算机中这通常意味着互斥仅仅是因为互斥访问很容易达到以上结果。想一下什么时候你会需要用到synchronized关键字:只有当一段代码修改了一些共享的数据的时候,你才有可能需要synchronized关键字。如果一段代码并没有改写共享的数据,你其实并不在乎是否有多个线程在或者不在同时执行一段代码。你真正在乎的只是一个非常简单的结果:如果你的代码对数据进行的修改,那么你的修改需要被依赖于该数据的线程完整的看到。当然现在的syncrhonized关键字实际上做到了两件事情:第一保证了数据的可见性,这点在多核cpu并且有非共享cpu缓存的时候很重要,第二防止了指令的无序调度。但最终synchronized并不是为了互斥,也不是为了并发,而是为了有序。

这个解读对于同步(synchronization)来说,应该是正确的。
同步指的是同步多个任务的某一时刻的状态,而不是指多个任务同步进行。

#其他 同步、异步、串行、并行、并发的概念,容后补充

以下内容转自: CSDN博主「小M姐姐呀~」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/baidu_41833099/article/details/123681483

1.认识进程与线程
首先需要了解一下进程和线程的概念。进程是系统资源分配和调度的基本单位,而线程是程序执行的最小单位。我们可以看成进程是由线程组成的。
在这里插入图片描述

2.串行的概念
串行是指一个时间段内,执行一个任务的同时不能执行其他任务,只能等到第一个任务弯沉过后才能进行第二个。比如排队上厕所,你只有等前一个人上完了,才可以进去。

3.并行的概念
并行是指一个时间段内,同时执行多个任务。比如操作系统中含有多个CPU,每个CPU可以同时执行多个任务。举个生活中的例子,边洗澡边唱歌,两件事情同时做,这就是并行

4.并发的概念
并发是值一个时间段内,两个或者两个以上任务都处于开启状态,而系统资源不够,采取的一种交替执行的方式,由于切换任务时间极短,感官上感觉不到,所以称为并发。比如说,在吃饭的时候,电话响了,此时我把碗放下去电话,接完电话再吃饭,这就是并发

在这里插入图片描述

5.什么是同步?
所谓同步是指发出一个功能调用时,在没有得到结果之前,该调用不返回。

在这里插入图片描述

同步的工作场景:
苦逼程序员

假设现在老板分配给了你一个很紧急并且很重要的任务,让你下班前必须完成(万恶的资本主义)。为了督促进度,老板搬了个椅子坐在一边盯着你写代码。
你心里肯定已经骂上了,“WTF,你有这么闲吗?盯着老子,你就不能去干点其他事情吗?”
老板仿佛接收到了你的脑电波一样:“我就在这等着,你写完前我哪也不去,厕所也不去。”
这个例子中老板交给你任务后就一直等待,什么都不做直到你写完,这个场景就是所谓的同步。

6.什么是异步?
异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

在这里插入图片描述

异步的工作场景:
第二天,老板又交给了你一项任务。
不过这次就没那么着急啦,这次老板轻描淡写,“小伙子可以啊,不错不错,你再努力干一年,明年我就财务自由了,今天的这个任务不着急,你写完告诉我一声就行”。
这次老板没有盯着你写代码,而是转身刷视频去了,你写完后简单的和老板报告一声“我写完了”。

在这个例子中老板交代完任务后不再一直等着什么都不做而是就去忙其它事情,你完成任务后简单的告诉老板任务完成,这就是所谓的异步。
值得注意的是,在异步这种场景下重点是在你写代码的同时老板在刷剧,这两件事在同时进行,而不是一方等待另一方,因此这就是为什么一般来说异步比同步高效的本质所在,不管同步异步应用在什么场景下。
我们可以看到同步这个词往往和任务的“依赖”、“关联”、“等待”等关键词相关,而异步往往和任务的“不依赖”,“无关联”,“无需等待”,“同时发生”等关键词相关。

7.个人理解

不知道你会不会有这样的疑问觉得异步和并发是一种东西,两者差距不大。事实上,这两者是两回事。我的理解是异步、同步是一种代码处理的方式,区别在程序调用时,根据是否等待返回结果后再进行下一步操作。串行、并发、并行更多是指操作系统在处理程序时才采用的方。

在这篇文章中我们来讨论一下到底什么是同步,什么是异步,以及在编程中这两个极为重要的概念到底意味着什么。

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

是的,这两个看上去很像实际上也很像的词汇给博主造成过很大的困扰,这两个词背后所代表的含义到底是什么呢?

我们先从工作场景讲起。

苦逼程序员

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

你心里肯定已经骂上了,“WTF,你有这么闲吗?盯着老子,你就不能去干点其他事情吗?”

老板仿佛接收到了你的脑电波一样:“我就在这等着,你写完前我哪也不去,厕所也不去。”
在这里插入图片描述

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

第二天,老板又交给了你一项任务。

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

这次老板没有盯着你写代码,而是转身刷视频去了,你写完后简单的和老板报告一声“我写完了”。
在这里插入图片描述

在这个例子中老板交代完任务后不再一直等着什么都不做而是就去忙其它事情,你完成任务后简单的告诉老板任务完成,这就是所谓的异步。

值得注意的是,在异步这种场景下重点是在你写代码的同时老板在刷剧,这两件事在同时进行,而不是一方等待另一方,因此这就是为什么一般来说异步比同步高效的本质所在,不管同步异步应用在什么场景下。

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

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

打电话与发邮件

作为一名苦逼的程序员是不能只顾埋头搬砖的,平时工作中的沟通免除不了,其中一种高效的沟通方式是吵架。。。啊不,是电话。
在这里插入图片描述

通常打电话时都是一个人在说另一个人听,一个人在说的时候另一个人等待,等另一个人说完后再接着说,因此在这个场景中你可以看到,“依赖”、“关联”、“等待”这些关键词出现了,因此打电话这种沟通方式就是所谓的同步。
在这里插入图片描述

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

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

同时当你写完邮件发出去后也不需要干巴巴的等着对方回复什么都不做,你也可以做一些像摸鱼之类这样有意义的事情

在这里插入图片描述

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

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

同步调用

现在终于回到编程的主题啦。

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

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

一般的函数调用都是同步的,就像这样:

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

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

从上图中我们可以看到,在funcB运行期间funcA什么都做不了,这就是典型的同步。

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

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

read(file, buf);

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

如图所示:
在这里插入图片描述

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

注意,和上面的同步调用不同的是,函数和被调函数运行在不同的线程中。

因此我们可以得出结论,同步调用和函数与被调函数是否运行在同一个线程是没有关系的。

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

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

但容易理解的代价就是在一些场景下,同步并不是高效的,原因很简单,因为任务没有办法同时进行。

接下来我们看异步调用。

异步调用

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

如果你真的理解了本节到目前为止的内容的话,那么异步调用对你来说不是问题。

一般来说,异步调用总是和I/O操作等耗时较高的任务如影随形,像磁盘文件读写、网络数据的收发、数据库操作等。

我们还是以磁盘文件读取为例。

在read函数的同步调用方式下,文件读取完之前调用方是无法继续向前推进的,但如果read函数可以异步调用情况就不一样了。

假如read函数可以异步调用的话,即使文件还没有读取完成,read函数也可以立即返回。

read(file, buff);// read函数立即返回// 不会阻塞当前程序

就像这样:
在这里插入图片描述

可以看到,在异步这种调用方式下,调用方不会被阻塞,函数调用完成后可以立即执行接下来的程序。

这时异步的重点就在于调用方接下来的程序执行可以和文件读取同时进行,从上图中我们也能看出这一点,这就是异步的高效之处。

但是,请注意,异步调用对于程序员来说在理解上是一种负担,代码编写上更是一种负担,总的来说,上帝在为你打开一扇门的时候会适当的关上一扇窗户。

有的同学可能会问,在同步调用下,调用方不再继续执行而是暂停等待,被调函数执行完后很自然的就是调用方继续执行,那么异步调用下调用方怎知道被调函数是否执行完成呢?

这就分为了两种情况:

调用方根本就不关心执行结果
调用方需要知道执行结果
第一种情况比较简单,无需讨论。

第二种情况下就比较有趣了,通常有两种实现方式:

一种是通知机制,也就是说当任务执行完成后发送信号来通知调用方任务完成,注意这里的信号有很多实现方式,Linux中的signal,或者使用信号量等机制都可以实现。

另一种是就是回调,也就是我们常说的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就可以了,这就是回调函数的作用。

也有的同学可能会有疑问,为什么这个函数要传递给数据库线程而不是数据库线程自己定义自己调用呢?

因为从软件组织结构上讲,这不是数据库线程该做的工作。

数据库线程需要做的仅仅就是查询数据库、然后调用一个处理函数,至于这个处理函数做了些什么数据库线程根本就不关心,也不应该关心。

你可以传入各种各样的回调函数。也就是说数据库系统可以针对回调函数这一抽象的函数变量来编程,从而更好的应对变化,因为回调函数的内容改变不会影响到数据库线程的逻辑,而如果数据库线程自己定义处理函数那么这种设计就没有灵活性可言了。

而从软件开发的角度看,假设数据库线程逻辑封装为了库提供给其它团队,当数据库团队在研发时怎么可能知道数据库查询后该做什么呢?

显然,只有使用方才知道查询完数据库后该做些什么,因此使用方在使用时简单的传入这个回调函数就可以了。

这样复杂数据库的团队就和使用方团队实现了所谓的解耦。

现在你应该明白回调函数的作用了吧。

不容易啊,容我喝口水叉会儿腰歇一歇。

我们继续。

另外仔细观察上面两张图,你能看出为什么异步比同步高效吗?

原因很简单,这也是我们在本篇提到过的,异步天然就无需等待,无依赖。

从上一张图中我们可以看到主线程的“休闲时光”不见了,取而代之的是不断的工作、工作、工作,就像苦逼的996程序员一样,而且数据库线程也没有那么大段大段的空闲了,取而代之的也是工作、工作、工作。
在这里插入图片描述

主线程处理请求和数据库处理查询请求可以同时进行,因此从系统性能上看,这样的设计能更加充分的利用系统资源,更加快速的处理请求;从用户的角度看,系统的响应也会更加迅速。

这就是异步的高效之处。

但我们应该也可以看出,异步编程并不如同步来的容易理解,系统可维护性上也不如同步模式。

那么有没有一种方法既能结合同步模式的容易理解又能结合异步模式的高效呢?答案是肯定的,我们将在后续章节详细讲解这一技术。

接下来我们看第二种情况,那就是主线程需要关心数据库查询结果。

  1. 主线程关心数据库操作结果

在这种情况下,数据库线程需要将查询结果利用通知机制发送给主线程,主线程在接收到消息后继续处理上一个请求的后半部分,就像这样:
在这里插入图片描述

从这里我们可以看到,ABCDEF几个步骤全部在主线中处理,同时主线程同样也没有了“休闲时光”,只不过在这种情况下数据库线程是比较清闲的,从这里并没有上一种方法高效,但是依然要比同步模式下要高效。

最后需要注意的是,并不是所有的情况下异步都一定比同步高效,还需要结合具体业务以及IO的复杂度具体情况具体分析。

总结

在这篇文章中我们从各种场景分析了同步与异步这两个概念,但是不管在什么场景下,同步往往意味着双方要相互等待、相互依赖,而异步意味着双方相互独立、各行其是。希望本篇能对大家理解这两个重要的概念有所帮助。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值