久久久久久久视色,久久电影免费精品,中文亚洲欧美乱码在线观看,在线免费播放AV片

<center id="vfaef"><input id="vfaef"><table id="vfaef"></table></input></center>

    <p id="vfaef"><kbd id="vfaef"></kbd></p>

    
    
    <pre id="vfaef"><u id="vfaef"></u></pre>

      <thead id="vfaef"><input id="vfaef"></input></thead>

    1. 站長資訊網(wǎng)
      最全最豐富的資訊網(wǎng)站

      解析PHP7下的協(xié)程是如何實現(xiàn)的

      本文由PHP7教程欄目給大家介紹關(guān)于PHP7下的協(xié)程如何實現(xiàn),希望對需要的朋友有所幫助!

      前言

      相信大家都聽說過『協(xié)程』這個概念吧。

      但是有些同學(xué)對這個概念似懂非懂,不知道怎么實現(xiàn),怎么用,用在哪,甚至有些人認(rèn)為yield就是協(xié)程!

      我始終相信,如果你無法準(zhǔn)確地表達(dá)出一個知識點的話,我可以認(rèn)為你就是不懂。

      如果你之前了解過利用PHP實現(xiàn)協(xié)程的話,你肯定看過鳥哥的那篇文章:在PHP中使用協(xié)程實現(xiàn)多任務(wù)調(diào)度| 風(fēng)雪之隅

      鳥哥這篇文章是從國外的作者翻譯來的,翻譯的簡潔明了,也給出了具體的例子了。

      我寫這篇文章的目的,是想對鳥哥文章做更加充足的補充,畢竟有部分同學(xué)的基礎(chǔ)還是不夠好,看得也是云頭霧里的。

      什么是協(xié)程

      先搞清楚,什么是協(xié)程。

      你可能已經(jīng)聽過『進(jìn)程』和『線程』這兩個概念。

      進(jìn)程就是二進(jìn)制可執(zhí)行文件在計算機(jī)內(nèi)存里的一個運行實例,就好比你的.exe文件是個類,進(jìn)程就是new出來的那個實例。

      進(jìn)程是計算機(jī)系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位(調(diào)度單位這里別糾結(jié)線程進(jìn)程的),每個CPU下同一時刻只能處理一個進(jìn)程。

      所謂的并發(fā),只不過是看起來CPU好像同時能處理幾件事情一樣,對于單核CPU事實上在用很快的速度切換不同的進(jìn)程。

      進(jìn)程的切換需要進(jìn)行系統(tǒng)調(diào)用,CPU要保存當(dāng)前進(jìn)程的各個信息,同時還會使CPUCache被廢掉。

      所以進(jìn)程切換不到非不得已就不做。

      那么怎么實現(xiàn)『進(jìn)程切換不到非不得已就不做』呢?

      首先進(jìn)程被切換的條件是:進(jìn)程執(zhí)行完畢、分配給進(jìn)程的CPU時間片結(jié)束,系統(tǒng)發(fā)生中斷需要處理,或者進(jìn)程等待必要的資源(進(jìn)程阻塞)等。你想下,前面幾種情況自然沒有什么話可說,但是如果是在阻塞等待,是不是就浪費了。

      其實阻塞的話我們的程序還有其他可執(zhí)行的地方可以執(zhí)行,不一定要傻傻的等!

      所以就有了線程。

      線程簡單理解就是一個『微進(jìn)程』,專門跑一個函數(shù)(邏輯流)。

      所以我們就可以在編寫程序的過程中將可以同時運行的函數(shù)用線程來體現(xiàn)了。

      線程有兩種類型,一種是由內(nèi)核來管理和調(diào)度。

      我們說,只要涉及需要內(nèi)核參與管理調(diào)度的,代價都是很大的。這種線程其實也就解決了當(dāng)一個進(jìn)程中,某個正在執(zhí)行的線程遇到阻塞,我們可以調(diào)度另外一個可運行的線程來跑,但是還是在同一個進(jìn)程里,所以沒有了進(jìn)程切換。

      還有另外一種線程,他的調(diào)度是由程序員自己寫程序來管理的,對內(nèi)核來說不可見。這種線程叫做『用戶空間線程』。

      協(xié)程可以理解就是一種用戶空間線程。

      協(xié)程,有幾個特點:

      • 協(xié)同,因為是由程序員自己寫的調(diào)度策略,其通過協(xié)作而不是搶占來進(jìn)行切換
      • 在用戶態(tài)完成創(chuàng)建,切換和銷毀
      • ⚠️ 從編程角度上看,協(xié)程的思想本質(zhì)上就是控制流的主動讓出(yield)和恢復(fù)(resume)機(jī)制
      • generator經(jīng)常用來實現(xiàn)協(xié)程

      說到這里,你應(yīng)該明白協(xié)程的基本概念了吧?

      PHP實現(xiàn)協(xié)程

      一步一步來,從解釋概念說起!

      可迭代對象

      PHP5提供了一種定義對象的方法使其可以通過單元列表來遍歷,例如用foreach語句。

      你如果要實現(xiàn)一個可迭代對象,你就要實現(xiàn)Iterator接口:

      <?php class MyIterator implements Iterator {     private $var = array();      public function __construct($array)     {         if (is_array($array)) {             $this->var = $array;         }     }      public function rewind() {         echo "rewindingn";         reset($this->var);     }      public function current() {         $var = current($this->var);         echo "current: $varn";         return $var;     }      public function key() {         $var = key($this->var);         echo "key: $varn";         return $var;     }      public function next() {         $var = next($this->var);         echo "next: $varn";         return $var;     }      public function valid() {         $var = $this->current() !== false;         echo "valid: {$var}n";         return $var;     } }  $values = array(1,2,3); $it = new MyIterator($values);  foreach ($it as $a => $b) {     print "$a: $bn"; }

      生成器

      可以說之前為了擁有一個能夠被foreach遍歷的對象,你不得不去實現(xiàn)一堆的方法,yield關(guān)鍵字就是為了簡化這個過程。

      生成器提供了一種更容易的方法來實現(xiàn)簡單的對象迭代,相比較定義類實現(xiàn)Iterator接口的方式,性能開銷和復(fù)雜性大大降低。

      <?php function xrange($start, $end, $step = 1) {     for ($i = $start; $i <= $end; $i += $step) {         yield $i;     } }   foreach (xrange(1, 1000000) as $num) {     echo $num, "n"; }

      記住,一個函數(shù)中如果用了yield,他就是一個生成器,直接調(diào)用他是沒有用的,不能等同于一個函數(shù)那樣去執(zhí)行!

      所以,yield就是yield,下次誰再說yield是協(xié)程,我肯定把你xxxx。

      PHP協(xié)程

      前面介紹協(xié)程的時候說了,協(xié)程需要程序員自己去編寫調(diào)度機(jī)制,下面我們來看這個機(jī)制怎么寫。

      0)生成器正確使用

      既然生成器不能像函數(shù)一樣直接調(diào)用,那么怎么才能調(diào)用呢?

      方法如下:

      1. foreach他
      2. send($value)
      3. current / next…

      1)Task實現(xiàn)

      Task就是一個任務(wù)的抽象,剛剛我們說了協(xié)程就是用戶空間線程,線程可以理解就是跑一個函數(shù)。

      所以Task的構(gòu)造函數(shù)中就是接收一個閉包函數(shù),我們命名為coroutine。

      /**  * Task任務(wù)類  */ class Task {     protected $taskId;     protected $coroutine;     protected $beforeFirstYield = true;     protected $sendValue;      /**      * Task constructor.      * @param $taskId      * @param Generator $coroutine      */     public function __construct($taskId, Generator $coroutine)     {         $this->taskId = $taskId;         $this->coroutine = $coroutine;     }      /**      * 獲取當(dāng)前的Task的ID      *       * @return mixed      */     public function getTaskId()     {         return $this->taskId;     }      /**      * 判斷Task執(zhí)行完畢了沒有      *       * @return bool      */     public function isFinished()     {         return !$this->coroutine->valid();     }      /**      * 設(shè)置下次要傳給協(xié)程的值,比如 $id = (yield $xxxx),這個值就給了$id了      *       * @param $value      */     public function setSendValue($value)     {         $this->sendValue = $value;     }      /**      * 運行任務(wù)      *       * @return mixed      */     public function run()     {         // 這里要注意,生成器的開始會reset,所以第一個值要用current獲取         if ($this->beforeFirstYield) {             $this->beforeFirstYield = false;             return $this->coroutine->current();         } else {             // 我們說過了,用send去調(diào)用一個生成器             $retval = $this->coroutine->send($this->sendValue);             $this->sendValue = null;             return $retval;         }     } }

      2)Scheduler實現(xiàn)

      接下來就是Scheduler這個重點核心部分,他扮演著調(diào)度員的角色。

      /**  * Class Scheduler  */ Class Scheduler {     /**      * @var SplQueue      */     protected $taskQueue;     /**      * @var int      */     protected $tid = 0;      /**      * Scheduler constructor.      */     public function __construct()     {         /* 原理就是維護(hù)了一個隊列,          * 前面說過,從編程角度上看,協(xié)程的思想本質(zhì)上就是控制流的主動讓出(yield)和恢復(fù)(resume)機(jī)制          * */         $this->taskQueue = new SplQueue();     }      /**      * 增加一個任務(wù)      *      * @param Generator $task      * @return int      */     public function addTask(Generator $task)     {         $tid = $this->tid;         $task = new Task($tid, $task);         $this->taskQueue->enqueue($task);         $this->tid++;         return $tid;     }      /**      * 把任務(wù)進(jìn)入隊列      *      * @param Task $task      */     public function schedule(Task $task)     {         $this->taskQueue->enqueue($task);     }      /**      * 運行調(diào)度器      */     public function run()     {         while (!$this->taskQueue->isEmpty()) {             // 任務(wù)出隊             $task = $this->taskQueue->dequeue();             $res = $task->run(); // 運行任務(wù)直到 yield              if (!$task->isFinished()) {                 $this->schedule($task); // 任務(wù)如果還沒完全執(zhí)行完畢,入隊等下次執(zhí)行             }         }     } }

      這樣我們基本就實現(xiàn)了一個協(xié)程調(diào)度器。

      你可以使用下面的代碼來測試:

      <?php function task1() {     for ($i = 1; $i <= 10; ++$i) {         echo "This is task 1 iteration $i.n";         yield; // 主動讓出CPU的執(zhí)行權(quán)     } }   function task2() {     for ($i = 1; $i <= 5; ++$i) {         echo "This is task 2 iteration $i.n";         yield; // 主動讓出CPU的執(zhí)行權(quán)     } }   $scheduler = new Scheduler; // 實例化一個調(diào)度器 $scheduler->addTask(task1()); // 添加不同的閉包函數(shù)作為任務(wù) $scheduler->addTask(task2()); $scheduler->run();

      關(guān)鍵說下在哪里能用得到PHP協(xié)程。

      function task1() {         /* 這里有一個遠(yuǎn)程任務(wù),需要耗時10s,可能是一個遠(yuǎn)程機(jī)器抓取分析遠(yuǎn)程網(wǎng)址的任務(wù),我們只要提交最后去遠(yuǎn)程機(jī)器拿結(jié)果就行了 */         remote_task_commit();         // 這時候請求發(fā)出后,我們不要在這里等,主動讓出CPU的執(zhí)行權(quán)給task2運行,他不依賴這個結(jié)果         yield;         yield (remote_task_receive());         ... }   function task2() {     for ($i = 1; $i <= 5; ++$i) {         echo "This is task 2 iteration $i.n";         yield; // 主動讓出CPU的執(zhí)行權(quán)     } }

      這樣就提高了程序的執(zhí)行效率。

      關(guān)于『系統(tǒng)調(diào)用』的實現(xiàn),鳥哥已經(jīng)講得很明白,我這里不再說明。

      3)協(xié)程堆棧

      鳥哥文中還有一個協(xié)程堆棧的例子。

      我們上面說過了,如果在函數(shù)中使用了yield,就不能當(dāng)做函數(shù)使用。

      所以你在一個協(xié)程函數(shù)中嵌套另外一個協(xié)程函數(shù):

      <?php function echoTimes($msg, $max) {     for ($i = 1; $i <= $max; ++$i) {         echo "$msg iteration $in";         yield;     } }   function task() {     echoTimes('foo', 10); // print foo ten times     echo "---n";     echoTimes('bar', 5); // print bar five times     yield; // force it to be a coroutine }   $scheduler = new Scheduler; $scheduler->addTask(task()); $scheduler->run();

      這里的echoTimes是執(zhí)行不了的!所以就需要協(xié)程堆棧。

      不過沒關(guān)系,我們改一改我們剛剛的代碼。

      把Task中的初始化方法改下,因為我們在運行一個Task的時候,我們要分析出他包含了哪些子協(xié)程,然后將子協(xié)程用一個堆棧保存。(C語言學(xué)的好的同學(xué)自然能理解這里,不理解的同學(xué)我建議去了解下進(jìn)程的內(nèi)存模型是怎么處理函數(shù)調(diào)用)

       /**      * Task constructor.      * @param $taskId      * @param Generator $coroutine      */     public function __construct($taskId, Generator $coroutine)     {         $this->taskId = $taskId;         // $this->coroutine = $coroutine;         // 換成這個,實際Task->run的就是stackedCoroutine這個函數(shù),不是$coroutine保存的閉包函數(shù)了         $this->coroutine = stackedCoroutine($coroutine);      }

      當(dāng)Task->run()的時候,一個循環(huán)來分析:

      /**  * @param Generator $gen  */ function stackedCoroutine(Generator $gen) {     $stack = new SplStack;      // 不斷遍歷這個傳進(jìn)來的生成器     for (; ;) {         // $gen可以理解為指向當(dāng)前運行的協(xié)程閉包函數(shù)(生成器)         $value = $gen->current(); // 獲取中斷點,也就是yield出來的值          if ($value instanceof Generator) {             // 如果是也是一個生成器,這就是子協(xié)程了,把當(dāng)前運行的協(xié)程入棧保存             $stack->push($gen);             $gen = $value; // 把子協(xié)程函數(shù)給gen,繼續(xù)執(zhí)行,注意接下來就是執(zhí)行子協(xié)程的流程了             continue;         }          // 我們對子協(xié)程返回的結(jié)果做了封裝,下面講         $isReturnValue = $value instanceof CoroutineReturnValue; // 子協(xié)程返回`$value`需要主協(xié)程幫忙處理                  if (!$gen->valid() || $isReturnValue) {             if ($stack->isEmpty()) {                 return;             }             // 如果是gen已經(jīng)執(zhí)行完畢,或者遇到子協(xié)程需要返回值給主協(xié)程去處理             $gen = $stack->pop(); //出棧,得到之前入棧保存的主協(xié)程             $gen->send($isReturnValue ? $value->getValue() : NULL); // 調(diào)用主協(xié)程處理子協(xié)程的輸出值             continue;         }          $gen->send(yield $gen->key() => $value); // 繼續(xù)執(zhí)行子協(xié)程     } }

      然后我們增加echoTime的結(jié)束標(biāo)示:

      class CoroutineReturnValue {     protected $value;       public function __construct($value) {         $this->value = $value;     }           // 獲取能把子協(xié)程的輸出值給主協(xié)程,作為主協(xié)程的send參數(shù)     public function getValue() {         return $this->value;     } }  function retval($value) {     return new CoroutineReturnValue($value); }

      然后修改echoTimes

      function echoTimes($msg, $max) {     for ($i = 1; $i <= $max; ++$i) {         echo "$msg iteration $in";         yield;     }     yield retval("");  // 增加這個作為結(jié)束標(biāo)示 }

      Task變?yōu)椋?/p>

      function task1() {     yield echoTimes('bar', 5); }

      這樣就實現(xiàn)了一個協(xié)程堆棧,現(xiàn)在你可以舉一反三了。

      4)PHP7中yield from關(guān)鍵字

      PHP7中增加了yield from,所以我們不需要自己實現(xiàn)攜程堆棧,真是太好了。

      把Task的構(gòu)造函數(shù)改回去:

          public function __construct($taskId, Generator $coroutine)     {         $this->taskId = $taskId;         $this->coroutine = $coroutine;         // $this->coroutine = stackedCoroutine($coroutine); //不需要自己實現(xiàn)了,改回之前的     }

      echoTimes函數(shù):

      function echoTimes($msg, $max) {     for ($i = 1; $i <= $max; ++$i) {         echo "$msg iteration $in";         yield;     } }

      task1生成器:

      function task1() {     yield from echoTimes('bar', 5); }

      這樣,輕松調(diào)用子協(xié)程。

      總結(jié)

      這下應(yīng)該明白怎么實現(xiàn)PHP協(xié)程了吧?

      建議不要使用PHP的Yield來實現(xiàn)協(xié)程,推薦使用swoole,2.0已經(jīng)支持了協(xié)程,并附帶了部分案例。

      End…

      贊(0)
      分享到: 更多 (0)
      網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號