協程是比較少見的概念,先轉過來做爲參考,須要時應該能夠用到
轉自:http://www.oschina.net/transl...php
生成器最基本的思想也是一個函數,這個函數的返回值是依次輸出,而不是隻返回一個單獨的值。或者,換句話說,生成器使你更方便的實現了迭代器接口。下面經過實現一個xrange函數來簡單說明:windows
<?php數組
<?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"; }
上面這個xrange()函數提供了和PHP的內建函數range()同樣的功能。可是不一樣的是range()函數返回的是一個包含屬組值從1到100萬的數組(注:請查看手冊)。而xrange()函數返回的是依次輸出這些值的一個迭代器,並且並不會真正以數組形式計算。app
這種方法的優勢是顯而易見的。它可讓你在處理大數據集合的時候不用一次性的加載到內存中。甚至你能夠處理無限大的數據流。函數
固然,也能夠不一樣經過生成器來實現這個功能,而是能夠經過繼承Iterator接口實現。經過使用生成器實現起來會更方便,而不用再去實現iterator接口中的5個方法了。oop
要從生成器認識協同程序,理解它們內部是如何工做的很是重要:生成器是可中斷的函數,在它裏面,yield構成了中斷點。 大數據
緊接着上面的例子,若是你調用xrange(1,1000000)的話,xrange()函數裏代碼沒有真正地運行。相反,PHP只是返回了一個實現了迭代器接口的 生成器類實例: this
<?php $range = xrange(1, 1000000); var_dump($range); // object(Generator)#1 var_dump($range instanceof Iterator); // bool(true)
協程操作系統
協程給上面功能添加的主要東西是回送數據給生成器的能力。這將把生成器到調用者的單向通訊轉變爲二者之間的雙向通訊。.net
<?php function logger($fileName) { $fileHandle = fopen($fileName, 'a'); while (true) { fwrite($fileHandle, yield . "\n"); } } $logger = logger(__DIR__ . '/log'); $logger->send('Foo'); $logger->send('Bar')
正如你能看到,這兒yield沒有做爲一個語句來使用,而是用做一個表達式。即它有一個返回值。yield的返回值是傳遞給send()方法的值。 在這個例子裏,yield將首先返回"Foo",而後返回"Bar"。
上面的例子裏yield僅做爲接收者。混合兩種用法是可能的,即既可接收也可發送。接收和發送通訊如何進行的例子以下:
<?php function gen() { $ret = (yield 'yield1'); var_dump($ret); $ret = (yield 'yield2'); var_dump($ret); } $gen = gen(); var_dump($gen->current()); // string(6) "yield1" var_dump($gen->send('ret1')); // string(4) "ret1" (the first var_dump in gen) // string(6) "yield2" (the var_dump of the ->send() return value) var_dump($gen->send('ret2')); // string(4) "ret2" (again from within gen) // NULL (the return value of ->send())
多任務協做
若是閱讀了上面的logger()例子,那麼你認爲「爲了雙向通訊我爲何要使用協程呢? 爲何我不能只用常見的類呢?」,你這麼問徹底正確。上面的例子演示了基本用法,然而上下文中沒有真正的展現出使用協程的優勢。這就是列舉許多協程例子的理由。正如上面介紹裏提到的,協程是很是強大的概念,不過這樣的應用很稀少並且經常十分複雜。給出一些簡單而真實的例子很難。
多任務協做這個術語中的「協做」說明了如何進行這種切換的:它要求當前正在運行的任務自動把控制傳回給調度器,這樣它就能夠運行其餘任務了。這與「搶佔」多任務相反,搶佔多任務是這樣的:調度器能夠中斷運行了一段時間的任務,無論它喜歡仍是不喜歡。協做多任務在Windows的早期版本(windows95)和Mac OS中有使用,不過它們後來都切換到使用搶先多任務了。理由至關明確:若是你依靠程序自動傳回 控制的話,那麼壞行爲的軟件將很容易爲自身佔用整個CPU,不與其餘任務共享。
這個時候你應當明白協程和任務調度之間的聯繫:yield指令提供了任務中斷自身的一種方法,而後把控制傳遞給調度器。所以協程能夠運行多個其餘任務。更進一步來講,yield能夠用來在任務和調度器之間進行通訊。
咱們的目的是 對 「任務」用更輕量級的包裝的協程函數:
<?php class Task { protected $taskId; protected $coroutine; protected $sendValue = null; protected $beforeFirstYield = true; public function __construct($taskId, Generator $coroutine) { $this->taskId = $taskId; $this->coroutine = $coroutine; } public function getTaskId() { return $this->taskId; } public function setSendValue($sendValue) { $this->sendValue = $sendValue; } public function run() { if ($this->beforeFirstYield) { $this->beforeFirstYield = false; return $this->coroutine->current(); } else { $retval = $this->coroutine->send($this->sendValue); $this->sendValue = null; return $retval; } } public function isFinished() { return !$this->coroutine->valid(); } }
任務ID標記
<?php function gen() { yield 'foo'; yield 'bar'; } $gen = gen(); var_dump($gen->send('something')); // As the send() happens before the first yield there is an implicit rewind() call, // so what really happens is this: $gen->rewind(); var_dump($gen->send('something')); // The rewind() will advance to the first yield (and ignore its value), the send() will // advance to the second yield (and dump its value). Thus we loose the first yielded value!
調度器如今不得不比多任務循環要作稍微多點了,而後才運行多任務:
<?php class Scheduler { protected $maxTaskId = 0; protected $taskMap = []; // taskId => task protected $taskQueue; public function __construct() { $this->taskQueue = new SplQueue(); } public function newTask(Generator $coroutine) { $tid = ++$this->maxTaskId; $task = new Task($tid, $coroutine); $this->taskMap[$tid] = $task; $this->schedule($task); return $tid; } public function schedule(Task $task) { $this->taskQueue->enqueue($task); } public function run() { while (!$this->taskQueue->isEmpty()) { $task = $this->taskQueue->dequeue(); $task->run(); if ($task->isFinished()) { unset($this->taskMap[$task->getTaskId()]); } else { $this->schedule($task); } } } }
<?php function task1() { for ($i = 1; $i <= 10; ++$i) { echo "This is task 1 iteration $i.\n"; yield; } } function task2() { for ($i = 1; $i <= 5; ++$i) { echo "This is task 2 iteration $i.\n"; yield; } } $scheduler = new Scheduler; $scheduler->newTask(task1()); $scheduler->newTask(task2()); $scheduler->run();
This is task 1 iteration 1. This is task 2 iteration 1. This is task 1 iteration 2. This is task 2 iteration 2. This is task 1 iteration 3. This is task 2 iteration 3. This is task 1 iteration 4. This is task 2 iteration 4. This is task 1 iteration 5. This is task 2 iteration 5. This is task 1 iteration 6. This is task 1 iteration 7. This is task 1 iteration 8. This is task 1 iteration 9. This is task 1 iteration 10.
既然調度器已經運行了,那麼咱們就轉向日程表的下一項:任務和調度器之間的通訊。咱們將使用進程用來和操做系統會話的一樣的方式來通訊:系統調用。咱們須要系統調用的理由是操做系統與進程相比它處在不一樣的權限級別上。所以爲了執行特權級別的操做(如殺死另外一個進程),就不得不以某種方式把控制傳回給內核,這樣內核就能夠執行所說的操做了。再說一遍,這種行爲在內部是經過使用中斷指令來實現的。過去使用的是通用的int指令,現在使用的是更特殊而且更快速的syscall/sysenter指令。
爲了說明系統調用,我將對可調用的系統調用作一個小小的封裝:
<?php class SystemCall { protected $callback; public function __construct(callable $callback) { $this->callback = $callback; } public function __invoke(Task $task, Scheduler $scheduler) { $callback = $this->callback; // Can't call it directly in PHP :/ return $callback($task, $scheduler); } }
<?php public function run() { while (!$this->taskQueue->isEmpty()) { $task = $this->taskQueue->dequeue(); $retval = $task->run(); if ($retval instanceof SystemCall) { $retval($task, $this); continue; } if ($task->isFinished()) { unset($this->taskMap[$task->getTaskId()]); } else { $this->schedule($task); } } }
<?php function getTaskId() { return new SystemCall(function(Task $task, Scheduler $scheduler) { $task->setSendValue($task->getTaskId()); $scheduler->schedule($task); }); }
<?php function task($max) { $tid = (yield getTaskId()); // <-- here's the syscall! for ($i = 1; $i <= $max; ++$i) { echo "This is task $tid iteration $i.\n"; yield; } } $scheduler = new Scheduler; $scheduler->newTask(task(10)); $scheduler->newTask(task(5)); $scheduler->run();
<?php function newTask(Generator $coroutine) { return new SystemCall( function(Task $task, Scheduler $scheduler) use ($coroutine) { $task->setSendValue($scheduler->newTask($coroutine)); $scheduler->schedule($task); } ); } function killTask($tid) { return new SystemCall( function(Task $task, Scheduler $scheduler) use ($tid) { $task->setSendValue($scheduler->killTask($tid)); $scheduler->schedule($task); } ); }
killTask函數須要在調度器裏增長一個方法:
<?php public function killTask($tid) { if (!isset($this->taskMap[$tid])) { return false; } unset($this->taskMap[$tid]); // This is a bit ugly and could be optimized so it does not have to walk the queue, // but assuming that killing tasks is rather rare I won't bother with it now foreach ($this->taskQueue as $i => $task) { if ($task->getTaskId() === $tid) { unset($this->taskQueue[$i]); break; } } return true; }
<?php function childTask() { $tid = (yield getTaskId()); while (true) { echo "Child task $tid still alive!\n"; yield; } } function task() { $tid = (yield getTaskId()); $childTid = (yield newTask(childTask())); for ($i = 1; $i <= 6; ++$i) { echo "Parent task $tid iteration $i.\n"; yield; if ($i == 3) yield killTask($childTid); } } $scheduler = new Scheduler; $scheduler->newTask(task()); $scheduler->run();
這段代碼將打印如下信息:
Parent task 1 iteration 1. Child task 2 still alive! Parent task 1 iteration 2. Child task 2 still alive! Parent task 1 iteration 3. Child task 2 still alive! Parent task 1 iteration 4. Parent task 1 iteration 5. Parent task 1 iteration 6.