我們知道多進程和多線程是實現併發的有效方式。但多進程的上下文切換資源開銷太大;多線程開銷相比要小不少,也是如今主流的作法,但其的控制權在內核,從而使用戶(程序員)失去了對代碼的控制,並且線程的上下文切換也是有必定開銷的。 這時爲了解決以上問題,"協程"(coroutine)的概念就產生了。你能夠將協程理解爲更輕量級的線程。這種線程叫作「用戶空間線程「。協程,有下面兩個特色:php
PHP對協程的支持是在迭代生成器的基礎上, 增長了能夠回送數據給生成器的功能(調用者發送數據給被調用的生成器函數)。 這就把生成器到調用者的單向通訊轉變爲二者之間的雙向通訊。html
迭代器的概念這裏就不贅述了。下面看看咱們本身實現的一個迭代器。程序員
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() { // 最後執行,就是執行完下面sleep(2)後再執行。(執行了next本次迭代纔算結束) $var = next($this->var); echo "next: $var\n"; return $var; } public function valid() { // 當valid返回false的時候迭代結束 $var = $this->current() !== false; echo "valid: {$var}\n"; return $var; } } $values = array(1,2,3,4); $it = new MyIterator($values); foreach ($it as $a => $b) { // 進行迭代(每次迭代,會依次執行如下方法: rewind(特別之處見上面解釋), valid, current, key, next) print "=====\n"; sleep(2); }
輸出:web
rewinding current: 1 // 由於valid裏面調用了current, 這裏current出來一次 valid: 1 current: 1 key: 0 ===== next: 2 current: 2 valid: 1 current: 2 key: 1 ===== next: 3 current: 3 valid: 1 current: 3 key: 2 ===== next: 4 current: 4 valid: 1 current: 4 key: 3 ===== next: current: valid: // valid返回false,迭代結束
有了yeild的方法就是一個生成器(生成器實現了Iterator接口,即一個生成器有迭代器的特色)。生成器的實現以下:服務器
function xrange($start, $end, $step = 1) { for ($i = $start; $i <= $end; $i += $step) { echo $i . "\n"; yield; } } // foreach方式 foreach (xrange(1, 10) as $num) { } $gene = xrange(1, 10); // gene就是一個生成器對象 // current $gene->current(); // 打印1 // next $gene->next(); $gene->current() // 打印2
輸出:多線程
1 2 3 4 5 6 7 8 9 10 1 2
生成器各方法詳解可看文檔: http://php.net/manual/zh/class.generator.php併發
注意:異步
生成器不能像函數同樣直接調用,調用方法以下:函數
1. foreach他this
2. send($value)
3. current / next...
yield的語法很靈活,咱們用下面的例子,讓你們能明白yield語法的使用。
function task1 () { for ($i = 1; $i <= 10; ++$i) { echo "This is task 1 iteration $i.\n"; yield;// 遇到yield就會主動讓出CPU的執行權; } } $a = task1(); $a->current(); // 執行第一次迭代 $a->send(1); // 喚醒當時讓出CPU執行權的yield
輸出:
This is task 1 iteration 1. This is task 1 iteration 2.
// yield返回 function task2 () { for ($i = 1; $i <= 10; ++$i) { echo "This is task 2 iteration $i.\n"; yield "lm$i"; // 遇到yield就會主動讓出CPU的執行權,for暫停執行, 而後返回"lm"。放在yield後面的值就是返回值 } } $a = task2(); $res = $a->current(); // 第一次迭代, 遇到yield返回 var_dump($res); $res = $a->send(1); // 喚醒yield, for繼續執行,遇到yield返回。 var_dump($res);
輸出:
This is task 2 iteration 1. string(3) "lm1" This is task 2 iteration 2. string(3) "lm2"
function task3 () { for ($i = 1; $i <= 10; ++$i) { echo "This is task 3 iteration $i.\n"; $getValue = yield;// 遇到yield就會主動讓出CPU的執行權;send後,將send值賦值給getValue echo $getValue . " "; } } $a = task3(); $a->current(); $a->send("aa"); // 喚醒yield,並將"aa"值賦值給$getValue變量
輸出:
This is task 3 iteration 1. aa This is task 3 iteration 2.
function task4 () { for ($i = 1; $i <= 10; ++$i) { echo "This is task 4 iteration $i.\n"; $ret = yield "lm$i"; // yield, 而後返回lm$i; 當send時,將send過來的值賦值給$ret; echo $ret; } } $a = task4(); var_dump($a->current()); // 返回lm1 var_dump($a->send("hhh ")); // 先喚醒yield, 將"hhh "賦值給$ret,再返回lm2 var_dump($a->send("www ")); // 先喚醒yield, 將"www "賦值給$ret,再返回lm3
輸出:
This is task 4 iteration 1. string(3) "lm1" hhh This is task 4 iteration 2. string(3) "lm2" www This is task 4 iteration 3. string(3) "lm3"
若是你有看過鳥哥的這篇文章http://www.laruence.com/2015/05/28/3038.html,應該對協程有個深入的認識。但裏面內容更適合中高級PHP工程師看,並且還得具有必定的操做系統的知識,因此我在此基礎上用更通俗的方式,闡明一下PHP的協程概念。協程很強大的功能但相對比較複雜, 也比較難被理解。我的目前尚未遇到合適的場景來使用PHP協程,不過我猜想,因爲能夠在用戶層面實現多併發,因此多用於CLI模式下的web服務開發,好比Golang的goroutine並非線程,而是協程。還有yield有雙向通訊的功能,因此還能夠實現異步服務,但須要本身寫調度器,好比鳥哥這篇博客裏面的非阻塞IOweb服務器就是靠協程實現異步了實現的。
以上內容若是有錯誤還請留言交流。