本篇內(nèi)容介紹了“PHP7的協(xié)程怎么實(shí)現(xiàn)”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)建站!專注于網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、小程序定制開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了高臺(tái)免費(fèi)建站歡迎大家使用!
先搞清楚,什么是協(xié)程。
你可能已經(jīng)聽過『進(jìn)程』和『線程』這兩個(gè)概念。
進(jìn)程就是二進(jìn)制可執(zhí)行文件在計(jì)算機(jī)內(nèi)存里的一個(gè)運(yùn)行實(shí)例,就好比你的.exe文件是個(gè)類,進(jìn)程就是new出來的那個(gè)實(shí)例。
進(jìn)程是計(jì)算機(jī)系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位(調(diào)度單位這里別糾結(jié)線程進(jìn)程的),每個(gè)CPU下同一時(shí)刻只能處理一個(gè)進(jìn)程。
所謂的并發(fā),只不過是看起來CPU好像同時(shí)能處理幾件事情一樣,對(duì)于單核CPU事實(shí)上在用很快的速度切換不同的進(jìn)程。
進(jìn)程的切換需要進(jìn)行系統(tǒng)調(diào)用,CPU要保存當(dāng)前進(jìn)程的各個(gè)信息,同時(shí)還會(huì)使CPUCache被廢掉。
所以進(jìn)程切換不到非不得已就不做。
那么怎么實(shí)現(xiàn)『進(jìn)程切換不到非不得已就不做』呢?
首先進(jìn)程被切換的條件是:進(jìn)程執(zhí)行完畢、分配給進(jìn)程的CPU時(shí)間片結(jié)束,系統(tǒng)發(fā)生中斷需要處理,或者進(jìn)程等待必要的資源(進(jìn)程阻塞)等。你想下,前面幾種情況自然沒有什么話可說,但是如果是在阻塞等待,是不是就浪費(fèi)了。
其實(shí)阻塞的話我們的程序還有其他可執(zhí)行的地方可以執(zhí)行,不一定要傻傻的等!
所以就有了線程。
線程簡單理解就是一個(gè)『微進(jìn)程』,專門跑一個(gè)函數(shù)(邏輯流)。
所以我們就可以在編寫程序的過程中將可以同時(shí)運(yùn)行的函數(shù)用線程來體現(xiàn)了。
線程有兩種類型,一種是由內(nèi)核來管理和調(diào)度。
我們說,只要涉及需要內(nèi)核參與管理調(diào)度的,代價(jià)都是很大的。這種線程其實(shí)也就解決了當(dāng)一個(gè)進(jìn)程中,某個(gè)正在執(zhí)行的線程遇到阻塞,我們可以調(diào)度另外一個(gè)可運(yùn)行的線程來跑,但是還是在同一個(gè)進(jìn)程里,所以沒有了進(jìn)程切換。
還有另外一種線程,他的調(diào)度是由程序員自己寫程序來管理的,對(duì)內(nèi)核來說不可見。這種線程叫做『用戶空間線程』。
協(xié)程可以理解就是一種用戶空間線程。
協(xié)程,有幾個(gè)特點(diǎn):
協(xié)同,因?yàn)槭怯沙绦騿T自己寫的調(diào)度策略,其通過協(xié)作而不是搶占來進(jìn)行切換
在用戶態(tài)完成創(chuàng)建,切換和銷毀
?? 從編程角度上看,協(xié)程的思想本質(zhì)上就是控制流的主動(dòng)讓出(yield)和恢復(fù)(resume)機(jī)制
generator經(jīng)常用來實(shí)現(xiàn)協(xié)程
說到這里,你應(yīng)該明白協(xié)程的基本概念了吧?
一步一步來,從解釋概念說起!
PHP5提供了一種定義對(duì)象的方法使其可以通過單元列表來遍歷,例如用foreach
語句。
你如果要實(shí)現(xiàn)一個(gè)可迭代對(duì)象,你就要實(shí)現(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 "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"; }
可以說之前為了擁有一個(gè)能夠被foreach
遍歷的對(duì)象,你不得不去實(shí)現(xiàn)一堆的方法,yield
關(guān)鍵字就是為了簡化這個(gè)過程。
生成器提供了一種更容易的方法來實(shí)現(xiàn)簡單的對(duì)象迭代,相比較定義類實(shí)現(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"; }
記住,一個(gè)函數(shù)中如果用了yield
,他就是一個(gè)生成器,直接調(diào)用他是沒有用的,不能等同于一個(gè)函數(shù)那樣去執(zhí)行!
所以,yield
就是yield
,下次誰再說yield
是協(xié)程,我肯定把你xxxx。
前面介紹協(xié)程的時(shí)候說了,協(xié)程需要程序員自己去編寫調(diào)度機(jī)制,下面我們來看這個(gè)機(jī)制怎么寫。
既然生成器不能像函數(shù)一樣直接調(diào)用,那么怎么才能調(diào)用呢?
方法如下:
foreach他
send($value)
current / next...
Task就是一個(gè)任務(wù)的抽象,剛剛我們說了協(xié)程就是用戶空間線程,線程可以理解就是跑一個(gè)函數(shù)。
所以Task的構(gòu)造函數(shù)中就是接收一個(gè)閉包函數(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),這個(gè)值就給了$id了 * * @param $value */ public function setSendValue($value) { $this->sendValue = $value; } /** * 運(yùn)行任務(wù) * * @return mixed */ public function run() { // 這里要注意,生成器的開始會(huì)reset,所以第一個(gè)值要用current獲取 if ($this->beforeFirstYield) { $this->beforeFirstYield = false; return $this->coroutine->current(); } else { // 我們說過了,用send去調(diào)用一個(gè)生成器 $retval = $this->coroutine->send($this->sendValue); $this->sendValue = null; return $retval; } } }
接下來就是Scheduler
這個(gè)重點(diǎn)核心部分,他扮演著調(diào)度員的角色。
/** * Class Scheduler */ Class Scheduler { /** * @var SplQueue */ protected $taskQueue; /** * @var int */ protected $tid = 0; /** * Scheduler constructor. */ public function __construct() { /* 原理就是維護(hù)了一個(gè)隊(duì)列, * 前面說過,從編程角度上看,協(xié)程的思想本質(zhì)上就是控制流的主動(dòng)讓出(yield)和恢復(fù)(resume)機(jī)制 * */ $this->taskQueue = new SplQueue(); } /** * 增加一個(gè)任務(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)入隊(duì)列 * * @param Task $task */ public function schedule(Task $task) { $this->taskQueue->enqueue($task); } /** * 運(yùn)行調(diào)度器 */ public function run() { while (!$this->taskQueue->isEmpty()) { // 任務(wù)出隊(duì) $task = $this->taskQueue->dequeue(); $res = $task->run(); // 運(yùn)行任務(wù)直到 yield if (!$task->isFinished()) { $this->schedule($task); // 任務(wù)如果還沒完全執(zhí)行完畢,入隊(duì)等下次執(zhí)行 } } } }
這樣我們基本就實(shí)現(xiàn)了一個(gè)協(xié)程調(diào)度器。
你可以使用下面的代碼來測(cè)試:
<?php function task1() { for ($i = 1; $i <= 10; ++$i) { echo "This is task 1 iteration $i.\n"; yield; // 主動(dòng)讓出CPU的執(zhí)行權(quán) } } function task2() { for ($i = 1; $i <= 5; ++$i) { echo "This is task 2 iteration $i.\n"; yield; // 主動(dòng)讓出CPU的執(zhí)行權(quán) } } $scheduler = new Scheduler; // 實(shí)例化一個(gè)調(diào)度器 $scheduler->addTask(task1()); // 添加不同的閉包函數(shù)作為任務(wù) $scheduler->addTask(task2()); $scheduler->run();
關(guān)鍵說下在哪里能用得到PHP協(xié)程。
function task1() { /* 這里有一個(gè)遠(yuǎn)程任務(wù),需要耗時(shí)10s,可能是一個(gè)遠(yuǎn)程機(jī)器抓取分析遠(yuǎn)程網(wǎng)址的任務(wù),我們只要提交最后去遠(yuǎn)程機(jī)器拿結(jié)果就行了 */ remote_task_commit(); // 這時(shí)候請(qǐng)求發(fā)出后,我們不要在這里等,主動(dòng)讓出CPU的執(zhí)行權(quán)給task2運(yùn)行,他不依賴這個(gè)結(jié)果 yield; yield (remote_task_receive()); ... } function task2() { for ($i = 1; $i <= 5; ++$i) { echo "This is task 2 iteration $i.\n"; yield; // 主動(dòng)讓出CPU的執(zhí)行權(quán) } }
這樣就提高了程序的執(zhí)行效率。
關(guān)于『系統(tǒng)調(diào)用』的實(shí)現(xiàn),鳥哥已經(jīng)講得很明白,我這里不再說明。
鳥哥文中還有一個(gè)協(xié)程堆棧的例子。
我們上面說過了,如果在函數(shù)中使用了yield
,就不能當(dāng)做函數(shù)使用。
所以你在一個(gè)協(xié)程函數(shù)中嵌套另外一個(gè)協(xié)程函數(shù):
<?php function 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->addTask(task()); $scheduler->run();
這里的echoTimes是執(zhí)行不了的!所以就需要協(xié)程堆棧。
不過沒關(guān)系,我們改一改我們剛剛的代碼。
把Task中的初始化方法改下,因?yàn)槲覀冊(cè)谶\(yùn)行一個(gè)Task的時(shí)候,我們要分析出他包含了哪些子協(xié)程,然后將子協(xié)程用一個(gè)堆棧保存。(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; // 換成這個(gè),實(shí)際Task->run的就是stackedCoroutine這個(gè)函數(shù),不是$coroutine保存的閉包函數(shù)了 $this->coroutine = stackedCoroutine($coroutine); }
當(dāng)Task->run()的時(shí)候,一個(gè)循環(huán)來分析:
/** * @param Generator $gen */ function stackedCoroutine(Generator $gen) { $stack = new SplStack; // 不斷遍歷這個(gè)傳進(jìn)來的生成器 for (; ;) { // $gen可以理解為指向當(dāng)前運(yùn)行的協(xié)程閉包函數(shù)(生成器) $value = $gen->current(); // 獲取中斷點(diǎn),也就是yield出來的值 if ($value instanceof Generator) { // 如果是也是一個(gè)生成器,這就是子協(xié)程了,把當(dāng)前運(yùn)行的協(xié)程入棧保存 $stack->push($gen); $gen = $value; // 把子協(xié)程函數(shù)給gen,繼續(xù)執(zhí)行,注意接下來就是執(zhí)行子協(xié)程的流程了 continue; } // 我們對(duì)子協(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é)程 } }
然后我們?cè)黾觘choTime的結(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 $i\n"; yield; } yield retval(""); // 增加這個(gè)作為結(jié)束標(biāo)示 }
Task
變?yōu)椋?/p>
function task1() { yield echoTimes('bar', 5); }
這樣就實(shí)現(xiàn)了一個(gè)協(xié)程堆棧,現(xiàn)在你可以舉一反三了。
PHP7中增加了yield from
,所以我們不需要自己實(shí)現(xiàn)攜程堆棧,真是太好了。
把Task的構(gòu)造函數(shù)改回去:
public function __construct($taskId, Generator $coroutine) { $this->taskId = $taskId; $this->coroutine = $coroutine; // $this->coroutine = stackedCoroutine($coroutine); //不需要自己實(shí)現(xiàn)了,改回之前的 }
echoTimes
函數(shù):
function echoTimes($msg, $max) { for ($i = 1; $i <= $max; ++$i) { echo "$msg iteration $i\n"; yield; } }
task1
生成器:
function task1() { yield from echoTimes('bar', 5); }
這樣,輕松調(diào)用子協(xié)程。
“PHP7的協(xié)程怎么實(shí)現(xiàn)”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
分享文章:PHP7的協(xié)程怎么實(shí)現(xiàn)
本文來源:http://www.rwnh.cn/article6/igehig.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導(dǎo)航、品牌網(wǎng)站設(shè)計(jì)、外貿(mào)建站、網(wǎng)站建設(shè)、標(biāo)簽優(yōu)化、品牌網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)