关于 同步异步,并发并行

前言

同步异步、并发并行,是设计高并发程序时绕不开的概念,同时也引申出一些话题。

比如:

  • 异步会让程序更快吗?
  • 并发真的能提升性能吗?
  • 并发和并行是同一概念吗?
  • 单核机器上,还有必要并发吗?

带着这些疑问,笔者在此写下自己的思考。

同步异步

通常形容方法调用。

对于同步方法调用,调用者必须等待方法执行完毕才能继续干后面的活儿。

对于异步方法调用,更像是触发一个事件传递,几乎在一瞬间完成,事件传递出去后,调用者就解放了,可以马上干后面的活儿,异步方法在后台默默执行。

对调用结果的处理

对于同步方法调用,因为调用者会等待方法执行完毕,所以除非发生异常,否则必然会拿到结果。

对于异步方法调用,因为方法是在后台执行,脱离了调用者,调用者是不知道方法何时执行完毕的。此时,针对异步调用的结果,通常有三种处理方式:

  • 不关心调用结果:最简单,无需处理
  • 提前返回占位符:异步调用后立即返回一个占位符,异步方法执行完毕会将结果放进去,调用者轮询获取。
  • 注册回调方法:异步调用的同时注册一个回调方法,异步方法执行完毕触发回调,好处是调用者无需轮询。

举个例子:小明要吃饭,前提是先得到一份饭。

得到饭,有两种方式。

方式一:小明亲力亲为,自己买菜,洗菜,做饭。这就是同步调用,小明在吃饭前,只能做饭,期间干不了其它任何事。

方式二:小明选择点外卖,在外卖送达前,自己写一会儿代码。这就是异步调用,小明点完外卖,自己就解放了,可以马上干其他活儿。

方式二的异步调用,小明要吃饭,所以自然是关心调用结果的。针对调用结果也可以有两种不同的处理方式。

比如,小明要求外卖骑手送达时,主动打电话给他。这就是"注册回调方法"的方式,异步方法完成时的主动通知。

再比如,小明要求外卖骑手不要打电话,外卖到了放门口。这就是"提前返回占位符"的方式,这里的"门口"就是占位符,小明也不知道外卖什么时候会送到,所以要时不时地去门口看一下,这就是"轮询结果"。

下面是 Java 实现同步方法和异步方法调用的示例,方法目标是对指定个数的随机数求和,异步方法有两个版本,对调用结果有两种处理方式。

java 复制代码
long syncCall(int size) {
    long sum = 0L;
    for (int i = 0; i < size; i++) {
        sum += ThreadLocalRandom.current().nextLong(1000L);
    }
    return sum;
}

// 返回占位符
Future<Long> asyncCall_V1(int size) {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    return executor.submit(() -> {
        long sum = 0L;
        for (int i = 0; i < size; i++) {
            sum += ThreadLocalRandom.current().nextLong(1000L);
        }
        return sum;
    });
}

// 注册回调方法
void asyncCall_V2(int size, Consumer<Long> callback) {
    new Thread(() -> {
        long sum = 0L;
        for (int i = 0; i < size; i++) {
            sum += ThreadLocalRandom.current().nextLong(1000L);
        }
        callback.accept(sum);
    }).start();
}

回到开头提出的问题,异步会让程序更快吗?

站在调用者的视角来看,异步调用在一瞬间完成,看起来程序更快了。但是从整体来看,程序并没有变快,异步任务也是要CPU去执行的。以小明吃饭为例,不管这个"饭"是小明自己做,还是点外卖由商家做,饭总是要有人去做的,小明从一开始到吃饭这个时间,并没有缩短,无非是异步版本下小明可以同时做其它事情。

并发并行

并发和并行,都表示任务同时执行,但是侧重点不同。

并发指多个任务"交替"执行,任务本身可能还是串行的。

一个CPU核心,同一时间只能执行一条指令,或者说一个线程/进程。那为什么在单核机器上,我们还能一边听音乐一边写代码呢?音乐进程 和 IDE 进程看起来就像同时在运行。

这就是并发模型,CPU 在音乐进程 和 IDE 进程之间来回切换,两个进程交替执行,只是时间短到人类无法感知。

并行,则表示真正意义上的同时执行。

因为CPU核心同一时间只能执行一条指令,所以对于单核CPU来说,是不存在并行的,真正的并行只可能存在于多核CPU下。

如前面的例子,在多核CPU下,CPU不用发生切换,就可以同时执行 IDE 进程和音乐进程。

还是以小明为例子。

小明外卖下单后,"做好饭并送达"的任务就落到商家头上了。如果商家先做饭,再自己去配送,这就是串行执行。

如果给商家两口锅,商家可以同时做几份饭,这是并发模型,商家不得不在两口锅之间来回切换。但是对于同一个任务,做饭 和 配送 之间,还是串行的,这不是真正的并行。

如果给商家分配一个骑手,商家只负责做饭,且在商家做饭的同时,骑手也在送餐,这才是并行,做饭和配送这两个子任务,真的是在同时进行。

回到开头提出的问题,并发真的能提升性能吗?

对于IO密集型应用来说,因为任务并不怎么消耗CPU,CPU大部分时间都在等IO,闲着也是闲着,这时可以考虑通过并发来提升性能。同时执行多个任务,让CPU在多个任务之间交替执行,闲着的CPU被充分利用起来。

对于CPU密集型应用来说,任务本身就非常消耗CPU了,此时再引入并发,只会得不偿失。因为CPU光是执行任务就已经忙不过来了,还要在多个任务之间切换,切换本身也是有开销的,性能反而会下降。

还是以厨师做饭为例,配2口锅能不能提升出餐速度,取决于做饭本身。如果做的饭不怎么需要颠勺,厨师可以在一口锅煮开的同时,去做第二口锅的饭,这肯定能提高出餐速度,因为这是IO密集型。如果做的饭需要一直颠勺,厨师本来就忙于颠勺,如果此时再让他去频繁换锅颠,只会影响出餐的速度,因为换锅本身也需要时间嘛,这是CPU密集型。

另一个问题 ,单核机器上,还有必要并发吗?

一个CPU核心同时只能执行一个线程,那在单核机器上,程序还有必要考虑并发吗?

这个问题和前一个问题本质上是类似的,如果是IO密集型应用,当然要考虑,并发可以提高CPU的利用率,进而提升性能。如果是CPU密集型应用,就不建议并发。

相关推荐
武子康1 小时前
Java-80 深入浅出 RPC Dubbo 动态服务降级:从雪崩防护到配置中心秒级生效
java·分布式·后端·spring·微服务·rpc·dubbo
舒一笑2 小时前
我的开源项目-PandaCoder迎来史诗级大更新啦
后端·程序员·intellij idea
@昵称不存在3 小时前
Flask input 和datalist结合
后端·python·flask
zhuyasen3 小时前
Go 分布式任务和定时任务太难?sasynq 让异步任务从未如此简单
后端·go
东林牧之3 小时前
Django+celery异步:拿来即用,可移植性高
后端·python·django
超浪的晨4 小时前
Java UDP 通信详解:从基础到实战,彻底掌握无连接网络编程
java·开发语言·后端·学习·个人开发
AntBlack4 小时前
从小不学好 ,影刀 + ddddocr 实现图片验证码认证自动化
后端·python·计算机视觉
Pomelo_刘金5 小时前
Clean Architecture 整洁架构:借一只闹钟讲明白「整洁架构」的来龙去脉
后端·架构·rust
双力臂4045 小时前
Spring Boot 单元测试进阶:JUnit5 + Mock测试与切片测试实战及覆盖率报告生成
java·spring boot·后端·单元测试
midsummer_woo6 小时前
基于spring boot的医院挂号就诊系统(源码+论文)
java·spring boot·后端