99热99这里只有精品6国产,亚洲中文字幕在线天天更新,在线观看亚洲精品国产福利片 ,久久久久综合网

歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋(píng)果6袋
6
麥子學(xué)院

在 PHP 中使用 Promise + co/yield 協(xié)程

發(fā)布時(shí)間:2016-12-21 16:07  回復(fù):0  查看:2918   最后回復(fù):2016-12-21 16:07  

我們知道 JavaScript 自從有了 Generator 之后,就有了各種基于 Generator 封裝的協(xié)程。其中hprose 中封裝的 Promise 和協(xié)程庫(kù)實(shí)現(xiàn)了跟 ES2016 的 async/await 一樣的功能,并且更加靈活。我們還知道 PHP語(yǔ)言自從 5.5 之后,也引入了 Generator,同樣也有了各種基于它封裝的PHP 協(xié)程庫(kù),hprose 同樣也為 PHP 提供的跟 JavaScript 版本類(lèi)似的 Promise 和協(xié)程庫(kù)。下面我們就來(lái)看一下它跟 swoole 結(jié)合的效果。

為什么需要異步方式

一個(gè)函數(shù)執(zhí)行之后,在它后面順序編寫(xiě)的代碼中,如果能夠直接使用它的返回結(jié)果或者它修改之后的引用參數(shù),那么我們通常認(rèn)為該函數(shù)是同步的。

而如果一個(gè)函數(shù)的執(zhí)行結(jié)果或者其修改的引用參數(shù),需要通過(guò)設(shè)置回調(diào)函數(shù)或者回調(diào)事件的方式來(lái)獲取,而在其后順序編寫(xiě)的代碼中無(wú)法直接獲取的話(huà),那么我們通常認(rèn)為這樣的函數(shù)是異步的。

PHP 提供的大部分函數(shù)都是同步的。通常我們會(huì)有一個(gè)誤解,那就是容易把同步和阻塞當(dāng)成同一個(gè)概念,但實(shí)際上同步代碼不一定都是阻塞的,只是同步代碼對(duì)阻塞天然友好,當(dāng)同步代碼和阻塞結(jié)合時(shí),代碼通常是簡(jiǎn)單易懂的。

阻塞帶來(lái)的問(wèn)題是當(dāng)前線(xiàn)程(或進(jìn)程)會(huì)陷入等待,一直等到阻塞結(jié)束,這樣就會(huì)造成線(xiàn)程(或進(jìn)程)資源的浪費(fèi)。所以,通常認(rèn)為阻塞是不夠高效的。

但是如果要編寫(xiě)非阻塞代碼,使用同步方式會(huì)變得有些復(fù)雜,且不夠靈活。同步方式的非阻塞代碼通常會(huì)使用 select 模式,例如 curl_multi_select , stream_select ,socket_select 等就是 PHP 中提供的一些典型的 select 式的函數(shù)。

我們說(shuō)它復(fù)雜且不夠靈活是有理由的,例如使用上面的 select 模式編寫(xiě)同步的非阻塞代碼時(shí),我們需要先構(gòu)造一個(gè)并發(fā)任務(wù)的列表,之后手動(dòng)構(gòu)造循環(huán)來(lái)執(zhí)行這些并發(fā)的任務(wù),在循環(huán)開(kāi)始之后,雖然這幾個(gè)任務(wù)可以并發(fā),但是這個(gè)循環(huán)相對(duì)于其后的代碼總體上仍然是阻塞的,我們要想拿到這些并發(fā)任務(wù)的結(jié)果時(shí),仍然需要等待。 select 雖然可以同時(shí)等待多個(gè)任務(wù)中某一個(gè)或幾個(gè)就位后,再執(zhí)行后續(xù)操作,但仍然有一部分時(shí)間是被等待消耗掉的。而且如果是純同步非阻塞的情況下,我們也很難在循環(huán)開(kāi)始后,動(dòng)態(tài)添加更多的任務(wù)到這個(gè)循環(huán)中去。

所以,如果我們希望程序能夠更加高效,更加靈活,就需要引入異步方式。

傳統(tǒng)的異步方式有什么問(wèn)題

一提到異步模式,大家腦子中的第一印象可能就是 回調(diào)、回調(diào)、回調(diào) 。是的,這是最簡(jiǎn)單最直接也是之前最常見(jiàn)的異步模式。只要在調(diào)用異步函數(shù)時(shí)設(shè)置一個(gè)或多個(gè)回調(diào)函數(shù),函數(shù)就會(huì)在完成時(shí)自動(dòng)調(diào)用回調(diào)函數(shù)?;蛘邽橐粋€(gè)對(duì)象設(shè)置一堆事件,之后調(diào)用該對(duì)象上的某個(gè)異步方法,雖然這個(gè)異步方法本身可能不再需要設(shè)置回調(diào)函數(shù),但是設(shè)置的這堆事件實(shí)際上跟回調(diào)函數(shù)所起到的作用是一樣的。

如果你的程序邏輯夠簡(jiǎn)單,簡(jiǎn)單的一兩層回調(diào)也許并不會(huì)讓你覺(jué)得異步方式的編程有什么麻煩。但如果你的程序邏輯一旦有些復(fù)雜,你可能就會(huì)被層層回調(diào)搞得疲憊不堪了。當(dāng)然,實(shí)際上你的程序需要層層回調(diào)的原因,也許并不是你的程序邏輯真的復(fù)雜,而是你沒(méi)有辦法將回調(diào)函數(shù)中的參數(shù)結(jié)果傳出來(lái),所以,你就不得不將另一個(gè)回調(diào)函數(shù)傳進(jìn)去。

我們來(lái)舉一個(gè)簡(jiǎn)單的例子,假設(shè)我們有 1 個(gè)同步函數(shù):

function sum($a, $b) {

return $a + $b;

}

然后我們按照下面的方式去調(diào)用它:

$a = sum(1, 2);

$b = sum($a, 3);

$c = sum($b, 4);

var_dump(array($a, $b, $c));

雖然上面的代碼很不精簡(jiǎn),但我們要表達(dá)的意圖很明確,而且代碼看起來(lái)很清楚。

那接下來(lái)我們把這個(gè)函數(shù)換成一個(gè)形式上的異步函數(shù),例如:

function async_sum($a, $b, $callback) {

$callback($a + $b);

}

當(dāng)然,它的執(zhí)行并不是異步的,這里我們先不關(guān)心它的實(shí)現(xiàn)是不是真異步的。

現(xiàn)在如果要做上面同樣的操作,代碼就要這樣寫(xiě)了:

async_sum(1, 2, function($a) {

async_sum($a, 3, function($b) use ($a) {

async_sum($b, 4, function($c) use ($a, $b) {

var_dump(array($a, $b, $c));

});

});

});

代碼的執(zhí)行結(jié)果是一樣的。但異步的代碼看起來(lái)顯然更難讀一些,雖然這已經(jīng)是很簡(jiǎn)單的例子了。

好了,看到這里,有些讀者可能會(huì)覺(jué)的我上面的這個(gè)例子很糟糕。因?yàn)槊髅饔型降暮瘮?shù)可以使用,并且代碼清晰可讀,為啥非要寫(xiě)個(gè)形似異步的函數(shù),把本來(lái)同步可以做的很好的事情用異步方式復(fù)雜化呢?而且那個(gè)異步調(diào)用的方式,最后不還是想要實(shí)現(xiàn)同步化的結(jié)果嗎?

如果你這么想的話(huà),一點(diǎn)都沒(méi)錯(cuò)。但我們這里想要解決的問(wèn)題是,如果我們拿到的只有一個(gè)異步函數(shù),這個(gè)函數(shù)沒(méi)有同步實(shí)現(xiàn),我們也不知道這個(gè)異步函數(shù)的內(nèi)部定義是怎樣的,我們也沒(méi)辦法將這個(gè)異步函數(shù)改為同步函數(shù)實(shí)現(xiàn)。那我們有沒(méi)有辦法將上面的程序改的更可讀一些呢?

當(dāng)然是可以的,所以,現(xiàn)在 Promise 要登場(chǎng)了。

為什么要引入 Promise

通常我們對(duì) Promise 的一個(gè)誤解就是,它要解決的是層層回調(diào)的問(wèn)題,比如上面的問(wèn)題看上去就是一個(gè)典型的層層回調(diào)的問(wèn)題。

然而實(shí)際上,Promise 要解決的并不是回調(diào)不回調(diào)的問(wèn)題,如果你使用過(guò) Promise 的話(huà),你會(huì)發(fā)現(xiàn)使用 Promise 你仍然少不了要使用回調(diào)。Promise 要解決的問(wèn)題是,如何將回調(diào)方法的參數(shù)從回調(diào)方法中傳遞出來(lái),讓它可以像同步函數(shù)的返回結(jié)果一樣,在回調(diào)函數(shù)以外的控制范圍內(nèi),可以傳遞和復(fù)用。

下面我們來(lái)看上面的例子用 Promise 如何解。

我們現(xiàn)在用最簡(jiǎn)單粗暴的方式來(lái)引入 Hprose 的庫(kù),直接復(fù)制源碼而不是使用 composer。然后我們?cè)诖a中直接使用:

<?phprequire_once("Hprose.php");use Hprose\Promise;

這種方式來(lái)引入 Hprose 的 Promise 庫(kù),當(dāng)然你也可以寫(xiě)成:

<?phprequire_once("Hprose.php");use Hprose\Future;

Future 庫(kù)跟 Promise 庫(kù)基本上是一樣的,你可以認(rèn)為 Future  Promise 的具體實(shí)現(xiàn), Promise 只是Future 實(shí)現(xiàn)的一個(gè)包裝。這個(gè)區(qū)別你可以從源碼中直接看出來(lái),這里就不多做解釋了。

接下來(lái),我們要把前面的 async_sum 函數(shù) Promise 化,Hprose 提供了這樣一個(gè)函數(shù):Promisepromisify (或者 Futurepromisify ),它的作用就是將一個(gè)使用回調(diào)方式的異步函數(shù)變成一個(gè)返回 Promise 對(duì)象的異步函數(shù)。這樣說(shuō),也許有些不好理解,下面直接上代碼:

<?phprequire_once("Hprose.php");

use Hprose\Promise;

function async_sum($a, $b, $callback) {

$callback($a + $b);

}

$sum = Promise\promisify(’async_sum’);

$a = $sum(1, 2);

$b = $a->then(function($a) use ($sum) {

return $sum($a, 3);

});

$c = $b->then(function($b) use ($sum) {

return $sum($b, 4);

});

Promise\all(array($a, $b, $c))->then(function($result) {

var_dump($result);

});

好了,看到這里,如果你對(duì) Promise 的理解還不夠深入的話(huà),你的第一反應(yīng)可能是:這不是把程序變得更復(fù)雜了嗎?原來(lái)的程序是 個(gè)回調(diào),現(xiàn)在仍然是 個(gè)回調(diào),還多了包裝,都玩出花來(lái)了,有意思嗎?

確實(shí),從上面的代碼來(lái)看,代碼并沒(méi)有被簡(jiǎn)化,但是你會(huì)發(fā)現(xiàn),現(xiàn)在回調(diào)函數(shù)中的參數(shù)已經(jīng)通過(guò) Promise 返回值的方式傳遞出來(lái)了,而且可以在原本的回調(diào)函數(shù)控制范圍以外被傳遞和復(fù)用了。

但是你可能會(huì)說(shuō)然并卵,程序不是仍然很復(fù)雜嗎?那我們就來(lái)進(jìn)一步簡(jiǎn)化一下:

<?phprequire_once("Hprose.php");

use Hprose\Promise;

function async_sum($a, $b, $callback) {

$callback($a + $b);

}

$sum = Promise\wrap(Promise\promisify(’async_sum’));

$var_dump = Promise\wrap(’var_dump’);

$a = $sum(1, 2);

$b = $sum($a, 3);

$c = $sum($b, 4);

$var_dump(Promise\all(array($a, $b, $c)));

現(xiàn)在,代碼中再也看不到回調(diào)了。因?yàn)槲覀儼押瘮?shù)包裝成了可以接收 Promise 變量的函數(shù)。當(dāng)然,其實(shí)現(xiàn)細(xì)節(jié)略微有些復(fù)雜,如果你感興趣,可以去看一下源碼,這里就不做源碼剖析了。如果感興趣的讀者多得話(huà),以后有時(shí)間再寫(xiě)源碼剖析。

當(dāng)然,如果你只是想把異步調(diào)用同步化,除了 Promisewrap 外,你還可以通過(guò) co/yield 協(xié)程來(lái)實(shí)現(xiàn)。

Hprose 中的 co/yield 協(xié)程

還是上面的例子,如果你使用的是 PHP 5.5 或者更高版本,那么你可以這樣來(lái)寫(xiě)代碼了。

<?phprequire_once("Hprose.php");

use Hprose\Promise;

function async_sum($a, $b, $callback) {

$callback($a + $b);

}

Promise\co(function() {

$sum = Promise\promisify(’async_sum’);

$a = (yield $sum(1, 2));

$b = (yield $sum($a, 3));

$c = (yield $sum($b, 4));

var_dump(array($a, $b, $c));

});

這代碼比使用 Promisewrap 的又要簡(jiǎn)單了。這里,代碼中的變量 $a , $b , $c 不再是 Promise 變量,而是實(shí)實(shí)在在的整數(shù)變量。也就是說(shuō), yield 把一個(gè) Promise 變量變成了一個(gè)普通變量。

現(xiàn)在 Promiseco 中的代碼已經(jīng)被實(shí)實(shí)在在的同步化了。

現(xiàn)在你可能有新的疑問(wèn)了,異步不是為了高效嗎?現(xiàn)在把原本的異步代碼同步化了,那還會(huì)高效嗎?

當(dāng)然,對(duì)這個(gè)例子上來(lái)說(shuō),效率肯定是沒(méi)有提高,反而是嚴(yán)重降低的。甚至在這個(gè)例子中,最原始的那個(gè)形似異步的實(shí)現(xiàn)也不比同步實(shí)現(xiàn)更高效。因?yàn)樵谶@個(gè)例子中,并沒(méi)有涉及到并發(fā)和 IO 阻塞的情況。

下面我們就放到真實(shí)場(chǎng)景下來(lái)看看 Promise 和 co/yield 協(xié)程是怎么用的。

 swoole 下使用 Promise 和 co/yield 協(xié)程

我們知道在 PHP 中,如果要讓程序延時(shí)可以使用 sleep 函數(shù)(或者 usleep , time_nanosleep 函數(shù))來(lái)讓程序阻塞一會(huì)兒,但是這個(gè)阻塞會(huì)讓整個(gè)進(jìn)程都阻塞,所以在阻塞期間,什么都不能干。

下面我們來(lái)看看使用 swoole_timer_after 實(shí)現(xiàn)的延時(shí)執(zhí)行:

<?phprequire_once("Hprose.php");

use Hprose\Future;

date_default_timezone_set(’UTC’);

function wait($time) {

$wait = Future\promisify(’swoole_timer_after’);

for ($i = 0; $i < 5; $i++) {

yield $wait($time);

var_dump("wait ". ($time / 1000) . "s, now is " . date("H:i:s"));

}

}

Future\co(wait(2000));

Future\co(wait(1000));

該程序執(zhí)行結(jié)果如下:

string(24) "wait 1s, now is 13:48:25"

string(24) "wait 2s, now is 13:48:26"

string(24) "wait 1s, now is 13:48:26"

string(24) "wait 1s, now is 13:48:27"

string(24) "wait 2s, now is 13:48:28"

string(24) "wait 1s, now is 13:48:28"

string(24) "wait 1s, now is 13:48:29"

string(24) "wait 2s, now is 13:48:30"

string(24) "wait 2s, now is 13:48:32"

string(24) "wait 2s, now is 13:48:34"

從結(jié)果中我們可以看出, wait(2000)  wait(1000) 各自都是順序阻塞執(zhí)行的,但是它們之間卻是并發(fā)執(zhí)行的。

也就是說(shuō),協(xié)程之間并不會(huì)相互阻塞,雖然這幾個(gè)并發(fā)的協(xié)程是在同一個(gè)進(jìn)程內(nèi)跑的。

最后我們?cè)賮?lái)看一個(gè)用 co/yield 協(xié)程實(shí)現(xiàn)的并發(fā)抓圖程序:

<?phprequire_once("Hprose.php");

use Hprose\Promise;

function fetch($url) {

$dns_lookup = Promise\promisify(’swoole_async_dns_lookup’);

$writefile = Promise\promisify(’swoole_async_writefile’);

$url = parse_url($url);

list($host, $ip) = (yield $dns_lookup($url[’host’]));

$cli = new swoole_http_client($ip, isset($url[’port’]) ? $url[’port’] : 80);

$cli->setHeaders([

’Host’ => $host,

"User-Agent" => ’Chrome/49.0.2587.3’,

]);

$get = Promise\promisify([$cli, ’get’]);

yield $get($url[’path’]);

list($filename) = (yield $writefile(basename($url[’path’]), $cli->body));

echo "write $filename ok.\r\n";

$cli->close();

}

$urls = array(

’http://b.hiphotos.baidu.com/baike/c0%3Dbaike116%2C5%2C5%2C116%2C38/sign=5f4519ba037b020818c437b303b099b6/472309f790529822434d08dcdeca7bcb0a46d4b6.jpg’,

’http://f.hiphotos.baidu.com/baike/c0%3Dbaike116%2C5%2C5%2C116%2C38/sign=1c37718b3cc79f3d9becec62dbc8a674/38dbb6fd5266d016dc2eaa5c902bd40735fa358a.jpg’,

’http://h.hiphotos.baidu.com/baike/c0%3Dbaike116%2C5%2C5%2C116%2C38/sign=edd05c9c502c11dfcadcb771024e09b5/d6ca7bcb0a46f21f3100c52cf1246b600c33ae9d.jpg’,

’http://a.hiphotos.baidu.com/baike/c0%3Dbaike92%2C5%2C5%2C92%2C30/sign=4693756e8094a4c21e2eef796f9d70b0/54fbb2fb43166d22df5181f5412309f79052d2a9.jpg’,

’http://a.hiphotos.baidu.com/baike/c0%3Dbaike92%2C5%2C5%2C92%2C30/sign=9388507144a98226accc2375ebebd264/faf2b2119313b07eb2cc820c0bd7912397dd8c45.jpg’,

);

foreach ($urls as $url) {

Promise\co(fetch($url));

}

在這個(gè)程序中, fetch 函數(shù)內(nèi)的代碼是同步執(zhí)行的,但是多個(gè) fetch 之間卻是并發(fā)執(zhí)行的,從結(jié)果輸出就可以看出來(lái),輸出順序是不一定的。但最后,你總能得到所有的美圖。

總結(jié):通過(guò) swoole 跟 hprose 中的 Promise 和 co/yield 協(xié)程相結(jié)合,你可以方便的使用同步的方式來(lái)調(diào)用 swoole 中的異步函數(shù)和方法,并可以實(shí)現(xiàn)協(xié)程間的并發(fā)。

 

來(lái)源:SegmentFault

您還未登錄,請(qǐng)先登錄

熱門(mén)帖子

最新帖子

?