填坑之PHP的yield和協程在一塊兒的日子裏(二)

首先是,這是我第一次把公衆號文章複製粘貼到sf.gg來。php

其次是,好久好久以前,我挖了一個yield的一個坑,本身挖的坑本身填,否則早晚會把本身埋掉。html

最後是,若是想看以前那個坑,請發送「yield」給文章末尾的公衆號,我開通了高大上的自動回覆功能,稀罕地不得了!node

PS:那篇文章中在最後我犯了一個錯誤,誤下了一個結論:foreach中不能使用send並猜想這是PHP的bug,實際上並非,真實的緣由粗暴簡單的理解就是send會讓生成器繼續執行一次致使。這件事情告訴咱們:git

除了裝逼以外,甩鍋也是有打臉風險的

那篇坑裏,內容和你能在百毒上搜索到的大多數文章都是差很少的,不過我那篇坑標題起得好:《yield是個什麼玩意(上)》,也就是暗示你們還有下篇,因此起標題也是須要必定技術含量的。github

我堅信,在座的各位辣雞在看完上篇坑文後最想說的註定是泰迪熊這句話(這是文化屬性,不以各位的意志而轉移):json

回到今天主旨上來,強調幾點:服務器

  • 雖然文章標題中有「yield和協程」這樣的關鍵字,但實際上yield並非協程,看起來有很多人直接將yield和協程劃了等號。yield的本質是生成器,英文名字叫作Generator。
  • yield只能用在function中,但用了yield就已經不是傳統意義上的function了,同時若是你企圖在function以外的其餘地方用yield,你會被打臉。
  • yield的最重要做用就是:本身中斷一坨代碼的執行,而後主動讓出CPU控制權給路人甲;而後又能經過一些方式從剛纔中斷的地方恢復運行。這個就比較屌了,假如你請求了一個費時10s的服務器API,此時是可讓出CPU給路人甲。粗暴地說上面的過程就算是協程的基本概念。

多線程和多進程都是操做系統參與的調度,而協程是用戶自主實現的調度,協程的關鍵點其實是「用戶層實現自主調度」,大概有「翻身農奴把歌唱」的意思。微信

下面我經過一坨代碼來體會一把「翻身農奴」,大家感覺一下:網絡

<?php
function gen1() {
  for( $i = 1; $i <= 10; $i++ ) {
    echo "GEN1 : {$i}".PHP_EOL;
    // sleep沒啥意思,主要就是運行時候給你一種切實的調度感,你懂麼
    // 就是那種「你看!你看!尼瑪,我調度了!臥槽」
    sleep( 1 );
    // 這句很關鍵,表示本身主動讓出CPU,我不下地獄誰下地獄
    yield;
  }
}
function gen2() {
  for( $i = 1; $i <= 10; $i++ ) {
    echo "GEN2 : {$i}".PHP_EOL;
    // sleep沒啥意思,主要就是運行時候給你一種切實的調度感,你懂麼
    // 就是那種「你看!你看!尼瑪,我調度了!臥槽」
    sleep( 1 );
    // 這句很關鍵,表示本身主動讓出CPU,我不下地獄誰下地獄
    yield;
  }
}
$task1 = gen1();
$task2 = gen2();
while( true ) {
  // 首先我運行task1,而後task1主動下了地獄
  echo $task1->current();
  // 這會兒我可讓task2介入進來了
  echo $task2->current();
  // task1恢復中斷
  $task1->next();
  // task2恢復中斷
  $task2->next();
}

上面代碼執行結果以下圖:多線程

雖然我話都說到這裏了,可是確定仍是有人get不到「因此,到底發生了什麼?」。你要知道,若是function gen1和function gen2中沒有yield,而是普通函數,你是沒法中斷其中的for循環的,諸以下面這樣的代碼:

<?php
function gen1() {
  for( $i = 1; $i <= 10; $i++ ) {
    echo "GEN1 : {$i}".PHP_EOL;
    sleep( 1 );
  }
}
function gen2() {
  for( $i = 1; $i <= 10; $i++ ) {
    echo "GEN2 : {$i}".PHP_EOL;
  }
}
gen1();
gen2();
// 看這裏,看這裏,看這裏!
// 上面的代碼一旦運行,必定是先運行完gen1函數中的for循環
// 其次才能運行完gen2函數中的for循環,絕對不會出現
// gen1和gen2交叉運行這種狀況

我彷佛已然精通了yield

寫到這裏後我也開始蹩了,和以往的憋了三天蹦不出來個屁有所不一樣,我此次蹩出了一個比較典型的應用場景:curl。下面咱們基於上面那坨辣雞代碼將gen1修改成一個耗時curl網絡請求,gen2將向一個文本文件中寫內容,咱們的目的就是在耗時的curl開始後主動讓出CPU,讓gen2去寫文件,以實現CPU的最大化利用。

<?php
$ch1 = curl_init();
// 這個地址中的php,我故意sleep了5秒鐘,而後輸出一坨json
curl_setopt( $ch1, CURLOPT_URL, "http://www.selfctrler.com/index.php/test/test1" );
curl_setopt( $ch1, CURLOPT_HEADER, 0 );
$mh = curl_multi_init();
curl_multi_add_handle( $mh, $ch1 );
function gen1( $mh, $ch1 ) {
  do {
    $mrc = curl_multi_exec( $mh, $running );
    // 請求發出後,讓出cpu
    yield;
  } while( $running > 0 );
  $ret = curl_multi_getcontent( $ch1 );
  echo $ret.PHP_EOL;
  return false;
}
function gen2() {
  for ( $i = 1; $i <= 10; $i++ ) {
    echo "gen2 : {$i}".PHP_EOL;
    file_put_contents( "./yield.log", "gen2".$i, FILE_APPEND );
    yield;
  }
}
$gen1 = gen1( $mh, $ch1 );
$gen2 = gen2();
while( true ) {
  echo $gen1->current();
  echo $gen2->current();
  $gen1->next();
  $gen2->next();
}

上面的代碼,運行之後,咱們再等待curl發起請求的5秒鐘內,同時能夠完成文件寫入功能,若是換作平時的PHP程序,就只能是先阻塞等待curl拿到結果後才能完成文件寫入。

文章太長,就像「老太太的裹腳布同樣,又臭又長」,因此,最後再對代碼作個極小幅度的改動就收尾不寫了!

<?php
$ch1 = curl_init();
// 這個地址中的php,我故意sleep了5秒鐘,而後輸出一坨json
curl_setopt( $ch1, CURLOPT_URL, "http://www.selfctrler.com/index.php/test/test1" );
curl_setopt( $ch1, CURLOPT_HEADER, 0 );
$mh = curl_multi_init();
curl_multi_add_handle( $mh, $ch1 );
function gen1( $mh, $ch1 ) {
  do {
    $mrc = curl_multi_exec( $mh, $running );
    // 請求發出後,讓出cpu
    $rs = yield;
    echo "外部發送數據{$rs}".PHP_EOL;
  } while( $running > 0 );
  $ret = curl_multi_getcontent( $ch1 );
  echo $ret.PHP_EOL;
  return false;
}
function gen2() {
  for ( $i = 1; $i <= 10; $i++ ) {
    echo "gen2 : {$i}".PHP_EOL;
    file_put_contents( "./yield.log", "gen2".$i, FILE_APPEND );
    $rs = yield;
    echo "外部發送數據{$rs}".PHP_EOL;
  }
}
$gen1 = gen1( $mh, $ch1 );
$gen2 = gen2();
while( true ) {
  echo $gen1->current();
  echo $gen2->current();
  $gen1->send("gen1");
  $gen2->send("gen2");
}

咱們修改了內容:

將$gen1->next()修改爲了$gen1->send("gen1")

在function gen1中yield有了返回值,而且將返回值打印出來

這件事情告訴咱們:yield和send,是能夠雙向通訊的,同時告訴咱們send能夠用來恢復原來中斷的代碼,並且在恢復中斷的同時能夠攜帶信息回去。寫到這裏,你是否是以爲這玩意的可利用價值是否是比原來高點兒了?

我知道,有人確定叨叨了:「老李,你代碼特麼寫的真是辣雞啊!你以前保證過了的 --- 只在公司生產環境寫辣雞代碼的。可你看看你這辣雞光環到籠罩都到demo裏了,你連demo都不放過了!你怎麼說?!」。兄dei,「又不是不能用」。並且我告訴你,上面這點兒curl demo來說明白yield仍是不夠的,後面還有兩三篇yield呢,照樣是爛代碼噁心死你,愛看不看。我勸你心放寬,你想一想你這麼爛的代碼都經歷了,還有什麼不能經歷的?

文章最後補個小故事:其實yield是PHP 5.5就已經添加進來了,這個模塊的做者叫作Nikita Popov,網絡上的名稱是Nikic。咱們知道PHP7這一代主力是惠新宸,下一代PHP主力就是Nikic了。早在2012年,Nikic就發表了一篇關於PHP yield多任務的文章,連接我貼出來你們共賞一下 --- http://nikic.github.io/2012/1...

最近開了一個微信公衆號,全部文章都在這裏

圖片描述

相關文章
相關標籤/搜索