我们或多或少听过异步这个词,这个词在前端javascrtipt编程中很常见,就是对于定时的或ajax请求任务,我们
不用等待,直接执行接下来的代码,直到之前的数据有返回。
我们来看个栗子:
console.log(new Date());
setTimeout(function(){
console.log(new Date());
console.log('111')
},5000);
setTimeout(function(){
console.log(new Date());
console.log('1114')
},3000);
console.log(new Date());
打印结果:
line:1 Thu Aug 10 2017 20:50:53 GMT+0800 (CST)
line:10 Thu Aug 10 2017 20:50:53 GMT+0800 (CST)
undefined
line:7 Thu Aug 10 2017 20:50:56 GMT+0800 (CST)
line:8 1114
line:3 Thu Aug 10 2017 20:50:58 GMT+0800 (CST)
line:4 111
我们看到第一行和最后一行的语句立马就打印出来了,但是第8行和第4行的结果则是等了5s才出来
很符合我们的预期,一个定时3s 一个定时5s,我们异步执行耗时以时间长的为准。
那么,实际上真是如此么?我们先来看看PHP中是怎么实先定时异步的。
感谢swoole,提供给了我们一个相同的机会实现了与js相同的定时器来实现定时异步:swoole安装教程
我们使用下面的代码:
function microtime_float()
{
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
function task1(){
echo "wait start" . PHP_EOL;
echo "wait end" . PHP_EOL;
echo microtime_float()."\n";
};
function task2(){
echo "Hello " . PHP_EOL;
echo "world!" . PHP_EOL;
echo microtime_float()."\n";
}
echo 'runstart:'.microtime_float()."\n";
swoole_timer_after(3000,'task1');
swoole_timer_after(2000,'task2');
echo 'runend:'.microtime_float()."\n";
打印结果:
runstart:1502370089.7314
runend:1502370089.7315
Hello
world!
1502370091.735
wait start
wait end
1502370092.7305
按实际的秒数计算确实只用了最长的秒数,所以事实正确了么,我们单进程的PHP变多进程同时执行多任务了么,
将上面的代码稍微改下,我们再看个栗子:
function microtime_float()
{
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
function task1(){
echo "wait start" . PHP_EOL;
echo "wait end" . PHP_EOL;
sleep(3);//3
echo microtime_float()."\n";
};
function task2(){
echo "Hello " . PHP_EOL;
echo "world!" . PHP_EOL;
sleep(3);//3
echo microtime_float()."\n";
}
echo 'runstart:'.microtime_float()."\n";
swoole_timer_after(3000,'task1');
swoole_timer_after(5000,'task2');
echo 'runend:'.microtime_float()."\n";
运行结果:
runstart:1502377883.6311
runend:1502377883.6312
wait start
wait end
1502377889.6352
Hello
world!
1502377893.6425
我们发现,实际的运行时间不是8s,而是10s
事实上,计算公式也绝非如此,异步的真正核心,在于用户态的调度,实际上始终是一个进程在做事,
没有像多进程那样,同时有多个进程帮你分担做事,我们看到的时间重合仅仅是用户态的定时器时间重合
真正做任务的时候,时间无法重合,因为我们至始至终只有一个进程在干活。
那么他的时间其实是:
a. 最小的延时时间 3s
b. 任务1延时到点了 主线程有没有空 有 3s
c. 任务2到点了 主线程有没有空 没有 继续等待
d. 等到任务1执行完成 主线程有空 任务2到点 任务2执行 3s
这里有点难理解为什么不是9s,而多出来1s,其实我也有点疑惑,定时器精确到毫秒级别,那么
程序是不是稳稳准准的在第6s结束执行任务2呢,任务2的定时器在5s的时候没有得到响应直接给自己加1s延时,
在第6s的时候,发现程序还没执行完,又给自己加了1s的延时,所以在第7s终于执行了,多了1s,这个解释可能
稍微合理些,swoole官方的定时器矫正没有给出相关的说明,但是提到了tick的矫正
那么实际上异步,只是执行阻塞任务时,保存用户态,让非阻塞的任务执行完了,再依次执行阻塞的任务,注意是依次,不是同时。
异步的优点就是:避开系统调度,重新生成用户上下文,减少这部分的时间和开销。
注意:swoole的异步io编程只能在cli模式下执行。