亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 語言 > PHP > 正文

PHP7下協程的實現方法詳解

2024-05-05 00:01:28
字體:
來源:轉載
供稿:網友

前言

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

但是有些同學對這個概念似懂非懂,不知道怎么實現,怎么用,用在哪,甚至有些人認為yield就是協程!

我始終相信,如果你無法準確地表達出一個知識點的話,我可以認為你就是不懂。

如果你之前了解過利用PHP實現協程的話,你肯定看過鳥哥的那篇文章:在PHP中使用協程實現多任務調度| 風雪之隅

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

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

什么是協程

先搞清楚,什么是協程。

你可能已經聽過『進程』和『線程』這兩個概念。

進程就是二進制可執行文件在計算機內存里的一個運行實例,就好比你的.exe文件是個類,進程就是new出來的那個實例。

進程是計算機系統進行資源分配和調度的基本單位(調度單位這里別糾結線程進程的),每個CPU下同一時刻只能處理一個進程。

所謂的并行,只不過是看起來并行,CPU事實上在用很快的速度切換不同的進程。

進程的切換需要進行系統調用,CPU要保存當前進程的各個信息,同時還會使CPUCache被廢掉。

所以進程切換不到費不得已就不做。

那么怎么實現『進程切換不到費不得已就不做』呢?

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

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

所以就有了線程。

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

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

線程有兩種類型,一種是由內核來管理和調度。

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

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

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

協程,有幾個特點:

  • 協同,因為是由程序員自己寫的調度策略,其通過協作而不是搶占來進行切換
  • 在用戶態完成創建,切換和銷毀
  • ?? 從編程角度上看,協程的思想本質上就是控制流的主動讓出(yield)和恢復(resume)機制
  • 迭代器經常用來實現協程

說到這里,你應該明白協程的基本概念了吧?

PHP實現協程

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

可迭代對象

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

你如果要實現一個可迭代對象,你就要實現Iterator接口:

<?phpclass MyIterator implements Iterator{ private $var = array(); public function __construct($array) {  if (is_array($array)) {   $this->var = $array;  } } public function rewind() {  echo "rewinding/n";  reset($this->var); } public function current() {  $var = current($this->var);  echo "current: $var/n";  return $var; } public function key() {  $var = key($this->var);  echo "key: $var/n";  return $var; } public function next() {  $var = next($this->var);  echo "next: $var/n";  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: $b/n";}

生成器

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

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

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

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

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

PHP協程

前面介紹協程的時候說了,協程需要程序員自己去編寫調度機制,下面我們來看這個機制怎么寫。

0)生成器正確使用

既然生成器不能像函數一樣直接調用,那么怎么才能調用呢?

方法如下:

  • foreach他
  • send($value)
  • current / next...

1)Task實現

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

所以Task的構造函數中就是接收一個閉包函數,我們命名為coroutine。

/** * Task任務類 */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; } /**  * 獲取當前的Task的ID  *   * @return mixed  */ public function getTaskId() {  return $this->taskId; } /**  * 判斷Task執行完畢了沒有  *   * @return bool  */ public function isFinished() {  return !$this->coroutine->valid(); } /**  * 設置下次要傳給協程的值,比如 $id = (yield $xxxx),這個值就給了$id了  *   * @param $value  */ public function setSendValue($value) {  $this->sendValue = $value; } /**  * 運行任務  *   * @return mixed  */ public function run() {  // 這里要注意,生成器的開始會reset,所以第一個值要用current獲取  if ($this->beforeFirstYield) {   $this->beforeFirstYield = false;   return $this->coroutine->current();  } else {   // 我們說過了,用send去調用一個生成器   $retval = $this->coroutine->send($this->sendValue);   $this->sendValue = null;   return $retval;  } }}

2)Scheduler實現

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

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

這樣我們基本就實現了一個協程調度器。

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

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

關鍵說下在哪里能用得到PHP協程。

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

這樣就提高了程序的執行效率。

關于『系統調用』的實現,鳥哥已經講得很明白,我這里不再說明。

3)協程堆棧

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

我們上面說過了,如果在函數中使用了yield,就不能當做函數使用。

所以你在一個協程函數中嵌套另外一個協程函數:

<?phpfunction echoTimes($msg, $max) { for ($i = 1; $i <= $max; ++$i) {  echo "$msg iteration $i/n";  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->newTask(task());$scheduler->run();

這里的echoTimes是執行不了的!所以就需要協程堆棧。

不過沒關系,我們改一改我們剛剛的代碼。

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

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

當Task->run()的時候,一個循環來分析:

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

然后我們增加echoTime的結束標示:

class CoroutineReturnValue { protected $value;  public function __construct($value) {  $this->value = $value; } // 獲取能把子協程的輸出值給主協程,作為主協程的send參數 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 $i/n";  yield; } yield retval(""); // 增加這個作為結束標示}

Task變為:

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

這樣就實現了一個協程堆棧,現在你可以舉一反三了。

4)PHP7中yield from關鍵字

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

把Task的構造函數改回去:

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

echoTimes函數:

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

task1生成器:

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

這樣,輕松調用子協程。

總結

這下應該明白怎么實現PHP協程了吧?

好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VeVb武林網的支持。


注:相關教程知識閱讀請移步到PHP教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲少妇激情视频| 欲色天天网综合久久| 国产精品日日做人人爱| 欧美激情亚洲一区| 欧美日韩亚洲一区二区| 欧美日韩免费网站| 国产精品第二页| 国产亚洲欧洲黄色| 亚洲精品av在线| 中文字幕精品久久| 91国产美女在线观看| 亚洲视频在线免费看| 日韩视频中文字幕| 国产视频久久久久久久| 亚洲a在线播放| 亚洲欧美日韩直播| 国内外成人免费激情在线视频| 日韩美女视频在线观看| 奇米4444一区二区三区| 欧美精品在线观看91| 欧洲美女免费图片一区| 国外日韩电影在线观看| 啪一啪鲁一鲁2019在线视频| 日韩在线观看视频免费| 国产精品美女呻吟| 欧美激情国产日韩精品一区18| 成人a在线视频| 欧美中文字幕精品| 日韩精品极品毛片系列视频| 成人免费视频网址| 亚洲影院高清在线| 亚洲精品mp4| 久久综合伊人77777蜜臀| 亚洲男女自偷自拍图片另类| 久久人人看视频| 热99精品只有里视频精品| 亚洲影院在线看| 国产亚洲美女精品久久久| 丝袜一区二区三区| 亚洲成人网在线| 国产一区二区视频在线观看| 欧美理论片在线观看| 亚洲一区二区三区乱码aⅴ| 国产成人自拍视频在线观看| 国产欧美精品久久久| 欧美激情一区二区三区在线视频观看| 亚洲欧美一区二区三区在线| 日韩小视频在线| 国产精彩精品视频| 日韩中文字幕在线播放| 亚洲999一在线观看www| 69久久夜色精品国产7777| 8050国产精品久久久久久| 国产一区二区三区在线| 欧美最猛性xxxxx亚洲精品| 一区二区三区日韩在线| 亚洲男人天堂手机在线| 欧美做受高潮电影o| 日韩av片电影专区| 亚洲国产欧美一区二区丝袜黑人| 91国偷自产一区二区三区的观看方式| 国产精品午夜一区二区欲梦| 日韩国产在线播放| 国产日本欧美一区二区三区| 亚洲精品天天看| 精品人伦一区二区三区蜜桃免费| 色妞在线综合亚洲欧美| 日韩电影免费在线观看中文字幕| 亚洲精品视频久久| 亚洲在线一区二区| 久久亚洲综合国产精品99麻豆精品福利| 欧美日韩国产限制| 欧美夫妻性生活xx| 亚洲欧洲日产国产网站| 亚洲激情自拍图| 国产最新精品视频| 国产小视频国产精品| 亚洲国产精品高清久久久| 亚洲自拍欧美另类| 精品国产一区av| 亚洲午夜av久久乱码| 精品久久久视频| 欧美色视频日本高清在线观看| 国产欧美亚洲视频| 欧美亚洲伦理www| 国产精品视频播放| 国产精品第2页| 亚洲精品国产精品乱码不99按摩| 美女精品久久久| 亚洲黄页网在线观看| 欧美在线性视频| 国产精品免费视频久久久| 久久99热精品| 97碰碰碰免费色视频| 国内精品小视频在线观看| 中文字幕日韩电影| 97国产精品人人爽人人做| 91国产中文字幕| 色综合亚洲精品激情狠狠| 成人免费自拍视频| 欧美一区第一页| 自拍偷拍亚洲一区| xxxxx91麻豆| 九九热99久久久国产盗摄| 黑人巨大精品欧美一区二区| 亚洲精品一区中文| 日韩精品久久久久久久玫瑰园| 亚洲一区www| 91成人性视频| 欧美做受高潮1| 日韩av在线免费看| 亚洲精品美女在线观看播放| 91在线精品视频| 日韩综合视频在线观看| 久久av红桃一区二区小说| 91麻豆桃色免费看| 精品少妇v888av| 日本一区二区不卡| 亚洲精品99久久久久中文字幕| 国产成人鲁鲁免费视频a| 在线观看不卡av| 国产精品国产福利国产秒拍| 国产+人+亚洲| 欧美日韩午夜视频在线观看| 亚洲精品黄网在线观看| 国产精品成人免费电影| 欧美成人午夜影院| 麻豆国产va免费精品高清在线| 久久激情视频久久| 色综合久综合久久综合久鬼88| 欧美精品亚州精品| 91精品国产综合久久香蕉的用户体验| 高跟丝袜一区二区三区| 成人h视频在线观看播放| 热re91久久精品国99热蜜臀| 伊人伊成久久人综合网站| 尤物九九久久国产精品的特点| 高清日韩电视剧大全免费播放在线观看| 亚洲欧美中文字幕| 国模gogo一区二区大胆私拍| 黑丝美女久久久| 一本色道久久88亚洲综合88| 国内精品久久影院| 亚洲欧洲高清在线| 亚洲国产精品系列| 成人精品一区二区三区电影黑人| 韩曰欧美视频免费观看| 日韩欧亚中文在线| 欧美一区二三区| 91av在线免费观看视频| 色偷偷888欧美精品久久久| 亚洲国产另类 国产精品国产免费| 91精品国产99久久久久久| 亚洲一二在线观看| 日本中文字幕不卡免费| 亚洲精品www久久久| 91久久久国产精品| 亚洲最新视频在线| 欧美激情精品久久久久久大尺度| 欧美性在线视频| 亚洲激情久久久| 日产精品99久久久久久| 91精品在线播放| 欧美一级免费视频|