PHP協程入門詳解

  • 概念

我們知道多進程和多線程是實現併發的有效方式。但多進程的上下文切換資源開銷太大;多線程開銷相比要小不少,也是如今主流的作法,但其的控制權在內核,從而使用戶(程序員)失去了對代碼的控制,並且線程的上下文切換也是有必定開銷的。 這時爲了解決以上問題,"協程"(coroutine)的概念就產生了。你能夠將協程理解爲更輕量級的線程。這種線程叫作「用戶空間線程「。協程,有下面兩個特色:php

  1. 協同。由於是由程序員本身寫的調度策略,其經過協做而不是搶佔來進行切換
  2. 在用戶態完成建立,切換和銷燬

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的語法很靈活,咱們用下面的例子,讓你們能明白yield語法的使用。

  • 用例1: 讓出cpu執行權
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.
  • 用例2: yield的返回
// 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"
  • 用例3: yield接收值
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.
  • 用例4: yeild接收和返回寫在一塊兒
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服務器就是靠協程實現異步了實現的。 

以上內容若是有錯誤還請留言交流。

相關文章
相關標籤/搜索