同步和异步、阻塞和非阻塞
注:这里的同步指的是同步调用,和Java的线程同步不一样,线程同步是为了防止多个线程修改共有资源时导致的资源不同步的问题
参考知乎的讨论:怎样理解阻塞非阻塞与同步异步的区别?
理解1
同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)
- 同步:调用方法之后在当前位置执行任务,执行完之后返回。
- 异步:调用方法之后不在当前位置执行任务,可以延后到某一个时刻(事件循环、delay)、或者在另一个线程执行,执行完之后通知当前位置。
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
- 阻塞:指调用结果返回之前,当前线程会被挂起,只有在得到结果之后才会返回。
- 非阻塞:指在不能立刻得到结果之前,该调用不会阻塞当前线程,当前线程继续往下执行。
区别:对于单次方法调用来说,阻塞意味着当前线程挂起,不占用cpu;同步代码会进入方法体中执行,占用cpu。
理解2
把异步换成通知,把阻塞换成等待。
- 有通知就是异步,没有通知就是同步
- 有等待就是阻塞,不等待就是非阻塞
Java的future.get()
,任务执行是异步的,但获取任务结果是阻塞的。
理解3
从通信的角度来说,同步=阻塞、异步=非阻塞
- 同步发送,发送方
send()
之后一直阻塞,直到消息到达 - 异步发送,发送方
send()
之后可以做其他事情,处于非阻塞状态 - 同步接收,接收方调用
receive()
方法会阻塞等待消息 - 异步接收,接收方调用
receive()
方法会立即返回一个结果。
总结
同步是通信术语,关注通信机制。阻塞和非阻塞是操作系统术语,关注程序运行状态。
同步和异步,阻塞和非阻塞,在不同上下文环境中,有不同的含义,需要具体分析。(结合下一篇I/O模型,理解会更深刻)
- 单线程和多线程
- 进程和线程
- 内核态和用户态
- 方法调用、I/O读写、通信
- 调用方和被调用方
- 方法内部和方法外部
- 发信方和收信方
- 宏观角度、微观角度
涉及到线程切换一般是异步,因为任务在另一个线程执行,执行完之后通知原线程。并且不会阻塞当前线程,除非出现锁竞争。
单线程的异步主要靠线程或者系统内核层面的轮询+消息队列,实现消息延迟处理。例如Android点击事件和Handler
setOnClickListener
、sendMessage
方法调用本身是同步的。这里的异步是指点击事件和消息事件,并且只针对当前的事件循环。在事件和消息处理的循环中,方法都是同步的。
方法调用最终都会化为同步,关键在于观察的视角
例如我们说的异步I/O,对于应用是异步的,但是在内核层面还是同步的。
例如启动新线程,任务是异步处理的,但启动线程这个方法是同步的。
(只不过一般不会这么描述而已,可以认为只要整个过程中包含异步我们就说它是异步的,不会把这个过程拆分开来描述)
例子:
- 同步阻塞:烧水,在旁边等水烧开,烧开之后取走使用
- 同步非阻塞:烧水,然后去做饭,时不时看看水烧开了没有,烧开之后取走使用
- 异步阻塞:烧水,设个闹钟,在旁边等水烧开,闹钟响了之后取走使用
- 异步非阻塞:烧水,设个闹钟,然后去做饭,闹钟响了之后取走使用
- I/O多路复用:
- select、poll:同时烧多壶水,设置多次闹钟,闹钟响了之后,检查所有的水壶(时间复杂度
O(n)
),取走烧好的水(只需要盯着闹钟,不需要盯着多壶水) - epoll:同时烧多壶水,设置多次闹钟,闹钟会报告哪一壶水烧烤了,取走烧好的水。(不需要检查所有的水壶,时间复杂度
O(1)
)
- select、poll:同时烧多壶水,设置多次闹钟,闹钟响了之后,检查所有的水壶(时间复杂度
同步和异步是针对水壶烧水(是否有通知),阻塞和非阻塞是针对人(烧水的时候是否可以干其他事情)。
关于异步阻塞很多地方说没有实际用处,我能想到的场景有两个(不确定案例是否合适):
- Thread的join方法,当前线程阻塞,线程执行完之后当前线程唤醒继续执行。
- Future异步返回,主动调用get的时候会阻塞等待结果。
异步和回调
异步:调用在发出之后,方法立即返回,但是结果还没返回。等待计算完结果后被调用者通过状态、消息通知来通知调用者,或通过回调函数处理这个调用。异步的核心在于延后处理。
回调:回头调用,将一个函数/接口作为参数传入,在特定时候调用参数方法。
我们常说的回调,按我的理解,包含三个含义:
- 回调是异步过程中的一个环节:调用-->等待结果-->回调结果
- 回调是异步的一种实现方式:这里指开发者自行实现的回调,Java的Future是另一种实现方式。
- 异步的本质就是回调:从定义上看,异步和回调都是延迟执行某个方法,异步指这个过程,回调指的是实现。下面提到的异步实现其实都用到了回调,区别只在于框架实现、OS实现、自行实现
注:异步可以是单线程,也可以是多线程。但有的异步框架可以方便的切换线程,实现跨线程通信。如Handler、协程、RxJava、EventBus等
常见异步实现方法
实现异步方式:
- 接口回调
- 消息通知:Handler、EventBus
- Future、Promise
- 可观察对象:LiveData、Observable
- 协程
- ...
写法上分为:嵌套写法、链式写法、同步写法
下面列举一些常见异步的实现方法,通过伪代码示例:延时1s模拟请求用户名称返回结果
自定义Callback接口回调
Callback在代码中可以是匿名内部类,也可以直接继承Callback接口去实现
interface Callback<T>{
fun onResult(result: T)
}
//被观察者
class UserModel {
fun getUserName(userId: Int, callback: Callback<String>) {
thread {
Thread.sleep(1000)
callback.onResult("Afauria")
}
}
}
//观察者
class UserPresenter {
fun getUserName() {
//匿名内部类
//实现接口
UserModel().getUserName(0, object: Callback<String>{
override fun onResult(result: String) {
println(result)
}
})
}
}
Android的Handler
class UserModel(private val mHandler: Handler) {
fun getUserName(userId: Int) {
thread {
Thread.sleep(1000)
mHandler.sendMessage(Message.obtain().apply {
what = 0x1
obj = "Afauria"
})
}
}
}
class UserPresenter : Handler.Callback {
private val mHandler = Handler(Looper.getMainLooper(), this)
override fun handleMessage(msg: Message): Boolean {
when (msg.what) {
0x1 -> {
println(msg.obj)
return true
}
}
return false
}
fun getUserName() {
UserModel(mHandler).getUserName(0)
}
}
RxJava的Observable、Flowable
class UserModel {
fun getUserName(userId: Int): Observable<String> {
return Observable.create { e ->
Thread.sleep(1000)
e.onNext("Afauria")
e.onComplete()
}
}
}
class UserPresenter {
fun getUserName() {
val disposable = UserModel().getUserName(0)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { name -> println(name) }
}
}
kotlin的协程
class UserModel {
suspend fun getUserName(userId: Int): String {
delay(1000)
return "Afauria"
}
}
class UserPresenter {
fun getUserName() {
GlobalScope.launch {
val userName = UserModel().getUserName(0)
withContext(Dispatcher.Main) {
print(userName)
}
}
}
}
Flutter的Future对象
class UserModel {
Future<String> getUser() {
return Future.delayed(Duration(milliseconds: 1000), () => "Afauria");
}
}
class UserPresenter {
getUser() async {
//使用async + await
final username = await UserModel().getUser();
print(username);
//使用Future
UserModel().getUser().then((value) => print(value));
}
}
EventBus事件总线
Android LiveData
JS Promise