同步异步,阻塞非阻塞,并发并行

发布于 2021-01-14  956 次阅读


首先讲一下与之相关的一些知识:

  • 同步&异步
  • 阻塞&非阻塞
  • 并发并行

同步&异步

我们以网络IO连接为例,先只考虑IO密集型,例如爬取2个网页的照片大致分为两步:

  1. 与网页建立IO连接,假设连接时间为T1
  2. 保存图片,假设连接时间为T2

那么对同步和异步就是两种工作模式了
同步:连接网页1 时花费 T1,保存图片花费 T2,然后再连接网页2。不能跳过网页1直接连接网页2,需要等待。

异步:在等待连接网页1所要花费的t1时间间隔内,去连接网页2。可以在等待IO连接时去执行其他任务。

可以看到,三个请求发送顺序与返回顺序,并不一样,这样就体现了异步请求。即我同时将请求发送出去,哪个先回来我先处理哪个。

我们可以理解为:我打电话的时候只允许和一个人通信,和这个人通信结束之后才允许和另一个人开始。这就是同步。

我们发短信的时候发完可以不去等待,去处理其他事情,当他回复之后我们再去处理,这样就大大解放了我们的时间。这就是异步。

体现在网页请求上面就是我请求一个网页时候等待他回复,否则不接收其它请求,这就是同步。另一种就是我发送请求之后不去等待他是否回复,而去处理其它请求,当处理完其他请求之后,某个请求说,我的回复了,然后程序转而去处理他的回复数据。这就是异步请求。所以,异步可以充分cpu的效率。

阻塞&非阻塞

阻塞:阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能够执行其他业务。等待当前函数返回

非阻塞:非阻塞和阻塞的概念相对应,非阻塞调用指在不能立刻得到结果之前也会立刻返回,同时该函数不会阻塞当前线程

阻塞和非阻塞指的是执行一个操作是等操作结束再返回,还是马上返回。

同步异步&阻塞非阻塞

根据上述同步异步和阻塞非阻塞的概念,我们很容易将【同步,阻塞】,【异步,非阻塞】搞混。

有些人常把同步阻塞和异步非阻塞联系起来,但实际上经过分析,阻塞与同步,非阻塞和异步的定义是不一样的。同步和异步的区别是遇到IO请求是否等待。阻塞和非阻塞的区别是数据没准备好的情况下是否立即返回

同步,阻塞

对于同步来说,很多时候当前线程可能还是激活的,只是从逻辑上当前函数没有返回而已,此时,这个线程可能也会处理其他的消息。还有一点,在这里先扩展下:

  • 如果这个线程在等待当前函数返回时,仍在执行其他消息处理,那这种情况就叫做同步非阻塞;
  • 如果这个线程在等待当前函数返回时,没有执行其他消息处理,而是处于挂起等待状态 ,那这种情况就叫做同步阻塞;

举个例子:比如我现在在(线程)排队买车票
我将做两件事情(线程里的任务)1.排队,2.玩游戏

  • 排队等待,买完车票以后再玩游戏,这就是同步阻塞,先完成任务1,再完成任务2
  • 同样在排队等待,不过在排队(任务1)的时候玩游戏(任务2),并且(调度者)时不时的抬头看看有没有排到我,这是同步非阻塞,我在排队的时候并不是什么也没干,而是在玩游戏(线程中的其他任务)

但是即使是非阻塞来说,对于同步非阻塞形式实际上是效率低下的,想象一下你一边打游戏还需要抬头看到底队伍排到你了没有。如果把打游戏和观察排队进行看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的。

异步,非阻塞

对于非阻塞和异步的概念有点混淆,非阻塞只是意味着方法调用不阻塞,就是说作为你不用为了排队而什么也不干,非阻塞的逻辑是"等可以读(写)了告诉你"(对应了你要时不时的抬头看排没排到你),但是完成读(写)工作的还是你自己(调度者)观察队伍有没有排到你的工作还是要你自己来做。

而异步意味这你可以不用亲自去做读(写)这件事,你的工作让别人(别的线程)来做,你只需要发起调用,别人把工作做完以后,或许再通知你,它的逻辑是“我做完了 告诉/不告诉 你”,他和非阻塞的区别在于一个是"已经做完"另一个是"可以去做"。

  • 我雇(雇这个动词代表发起调用)了一个人(另一个线程)帮我排队,排到我通知我一声,而我就舒舒服服的坐在那打游戏。排队观察队伍的工作已经不需要我做了

异步非阻塞形式就不存在同步非阻塞那样的问题,因为打游戏是你的事,而通知你则是服务人员的事情(触发机制),程序没有在两种不同的操作中来回切换。

并发&并行

并发和并行一直是容易混淆的概念。并发通常指有多个任务需要同时进行,并行则是同一时刻有多个任务执行。用上课来举例就是,并发情况下是一个老师在同一时间段辅助不同的人功课。并行则是好几个老师分别同时辅助多个学生功课。简而言之就是一个人同时吃三个馒头还是三个人同时分别吃一个的情况,吃一个馒头算一个任务。

asyncio实现并发,就需要多个协程来完成任务,每当有任务阻塞的时候就await,然后其他协程继续工作。创建多个协程的列表,然后将这些协程注册到事件循环中。

小结

其实异步还可以分为两种:带通知的和不带通知的。前面说的那种属于带通知的。你雇的人可能会犯困,会忘记通知你,你就需要时不时的去关注一下状态。这种就是不带通知的异步。

再举一个例子:

  去书店借一本书,同步就是我要亲自到书店,问老板有没有这本书,阻塞就是老板查询的时候(读写)我只能在那等着,老板找到书后把书交给我,这就是同步阻塞。

  我亲自到书店借书,老板在找这本书的时候,我可以去干别的,然后每隔一段时间去问老板书找到了没有,也可以等老板找到书以后通知我,这就是同步非阻塞。

  我想借本书,找个人帮我去借,借到书以后再通知我,这就是异步,我只发起调用,但是本身并不参与这个事件,而是让别的线程去做这个事。

  同步与异步是对应的,它们是线程之间的关系,两个线程之间要么是同步的,要么是异步的。

  阻塞与非阻塞是对同一个线程来说的,在某个时刻,线程要么处于阻塞,要么处于非阻塞。

  帮我借书的那个人有没有借到书,我可以打电话问他(轮询),也可以等他通知我,这是异步的通知;在借书的过程中借书的那个人可以轮询的方式查看书是否已经找到(缓冲区有没有数据),找到了你可以把它拿走,也可以等老板找到书后通知我,这是非阻塞的通知与轮询。


在python中,协程是异步非阻塞,而线程池是全程阻塞式的。

相关博客:
https://zhuanlan.zhihu.com/p/65336603
https://www.cnblogs.com/zhangyafei/p/9606765.html


所念皆星河