对于iOS和Android 原生平台来说,如果要做一个耗时操作,比如网络请求,图片处理等,都会开一个子线程,等待数据处理完成,再在主线程中进行UI的显示和刷新,但是,Dart是一门单线程编程语言。
Dart是如何做到不卡住主线程的呢?
答案就是异步IO
+事件循环
。
I/O模型
注: IO 模型是操作系统层面的,以下代码都是伪代码,只是为了方便理解。
我们先来看看阻塞IO是什么样的:
int count = io.read(buffer); //阻塞等待
当相应线程调用了read
之后,它就会一直在那里等着结果返回,什么也不干,这是阻塞式的IO。
但我们的应用程序经常是要同时处理很多个IO的,有人说,这种情况可以使用多线程啊,这确实是个思路,但受制于CPU的实际并发数,每个线程只能同时处理单个IO,性能限制还是很大,而且还要处理不同线程之间的同步问题,程序的复杂度大大增加。
如果进行IO的时候不用阻塞,那情况就不一样了:
while(true){
for(io in io_array){
status = io.read(buffer);// 不管有没有数据都立即返回
if(status == OK){
}
}
}
有了非阻塞IO,通过轮询的方式,我们就可以对多个IO进行同时处理了,但这样也有一个明显的缺点:在大部分情况下,IO都是没有内容的(CPU的速度远高于IO速度),这样就会导致CPU大部分时间在空转,计算资源依然没有很好得到利用。
为了进一步解决这个问题,人们设计了IO多路转接(IO multiplexing),可以对多个IO监听和设置等待时间:
while(true){
//如果其中一路IO有数据返回,则立即返回;如果一直没有,最多等待不超过timeout时间
status = select(io_array, timeout);
if(status == OK){
for(io in io_array){
io.read() //立即返回,数据都准备好了
}
}
}
有了IO多路转接,CPU资源利用效率又有了一个提升。
在上面的代码中,线程依然是可能会阻塞在 select
上或者产生一些空转的,有没有一个更加完美的方案呢?
答案就是异步IO了:
io.async_read((data) => {
// dosomething
});
通过异步IO,我们就不用不停问操作系统:你们准备好数据了没?而是一有数据系统就会通过消息或者回调的方式传递给我们。这看起来很完美了,但不幸的是,不是所有的操作系统都很好地支持了这个特性,比如Linux的异步IO就存在各种缺陷,所以在具体的异步IO实现上,很多时候可能会折中考虑不同的IO模式,比如 Node.js 的背后的libeio库,实质上采用线程池与阻塞 I/O 模拟出来的异步 I