上篇我們講到,無論我怎么調高重試次數,數據總是沒有準備好。
我期望的異步動態模型是這樣的:
然而真實的場景不是這樣的。通過調試,我大致了解到實際的模型應該是這樣的:
也就是說,無論我怎么提高重試次數,數據永遠不會準備好,數據只有在當前函數準備好以后,才會開始執行,這里的異步,只是減少了準備連接的時間。
那么問題來了,我該如何讓程序在準備數據之后執行我期望的功能呢。
先看一下Swoole官方執行異步任務的代碼是如何寫的
$serv = new swoole_server("127.0.0.1", 9501);
//設置異步任務的工作進程數量
$serv->set(array('task_worker_num' => 4));
$serv->on('receive', function($serv, $fd, $from_id, $data) {
//投遞異步任務
$task_id = $serv->task($data);
echo "Dispath AsyncTask: id=$task_id\n";
});
//處理異步任務
$serv->on('task', function ($serv, $task_id, $from_id, $data) {
echo "New AsyncTask[id=$task_id]".PHP_EOL;
//返回任務執行的結果
$serv->finish("$data -> OK");
});
//處理異步任務的結果
$serv->on('finish', function ($serv, $task_id, $data) {
echo "AsyncTask[$task_id] Finish: $data".PHP_EOL;
});
$serv->start();
代碼2.1
可以看到,官方是通過一個function匿名函數,將后續的執行邏輯傳了進去。這么看,事情就變得簡單多了。
<?php
/**
* Class Crawler
* Path: /Async/Crawler.php
*/
class Crawler
{
private $url;
private $toVisit = [];
public function __construct($url)
{
$this->url = $url;
}
public function visitOneDegree()
{
$this->visit($this->url, function ($content) {
$this->loadPage($content);
$this->visitAll();
});
}
private function loadPage($content)
{
$pattern = '#((http|ftp)://(\S*?\.\S*?))([\s)\[\]{},;"\':<]|\.\s|$)#i';
preg_match_all($pattern, $content, $matched);
foreach ($matched[0] as $url) {
if (in_array($url, $this->toVisit)) {
continue;
}
$this->toVisit[] = $url;
}
}
private function visitAll()
{
foreach ($this->toVisit as $url) {
$this->visit($url);
}
}
private function visit($url, $callBack = null)
{
$urlInfo = parse_url($url);
Swoole\Async::dnsLookup($urlInfo['host'], function ($domainName, $ip) use($urlInfo, $callBack) {
if (!$ip) {
return;
}
$cli = new swoole_http_client($ip, 80);
$cli->setHeaders([
'Host' => $domainName,
"User-Agent" => 'Chrome/49.0.2587.3',
'Accept' => 'text/html,application/xhtml+xml,application/xml',
'Accept-Encoding' => 'gzip',
]);
$cli->get($urlInfo['path'], function ($cli) use ($callBack) {
if ($callBack) {
call_user_func($callBack, $cli->body);
}
$cli->close();
});
});
}
}
代碼2.2
看了這段代碼,竟然有種似曾相識的感覺,在nodejs開發中,隨處可見的callback原來是有它的道理的。現在我才突然明白,原來callback的存在就是為了解決異步問題的。
執行了一下程序,竟然只用0.0007s,還沒開始就已經結束了!異步的效率真的能提升這么多嗎?答案當然是否定的,是我們的代碼出問題了。
由于用了異步,沒有等任務完全跑完,就已經執行了計算結束的時間的邏輯。看來又到了用callback的時候了。
/**
Async/Crawler.php
**/
public function visitOneDegree($callBack)
{
$this->visit($this->url, function ($content) use($callBack) {
$this->loadPage($content);
$this->visitAll();
call_user_func($callBack);
});
}
代碼2.3
<?php
/**
* crawler.php
*/
require_once 'Async/Crawler.php';
$start = microtime(true);
$url = 'http://www.swoole.com/';
$ins = new Crawler($url);
$ins->visitOneDegree(function () use($start) {
$timeUsed = microtime(true) - $start;
echo "time used: " . $timeUsed;
});
/*output:
time used: 0.068463802337646
*/
代碼2.4
現在來看,結果可信多了。
讓我們比較一下同步的異步的差距,同步耗時6.26s,異步耗時0.068秒,差了整整6.192s。不,更準確地表述,應該是差了將近10倍!
當然,從效率上講,異步遠遠高于同步的代碼,但是從邏輯上講,異步的邏輯比同步更繞,代碼中會帶來大量的callback,不便于理解。
Swoole官方里有一段關于異步與同步的選擇的描述,非常中肯,分享給大家:
我們不贊成用異步回調的方式去做功能開發,傳統的PHP同步方式實現功能和邏輯是最簡單的,也是最佳的方案。像node.js這樣到處callback,只是犧牲可維護性和開發效率。