zoukankan      html  css  js  c++  java
  • PHP协程入门详解

     概念

    咱们知道多进程和多线程是实现并发的有效方式。但多进程的上下文切换资源开销太大;多线程开销相比要小很多,也是现在主流的做法,但其的控制权在内核,从而使用户(程序员)失去了对代码的控制,而且线程的上下文切换也是有一定开销的。 这时为了解决以上问题,"协程"(coroutine)的概念就产生了。你可以将协程理解为更轻量级的线程。这种线程叫做“用户空间线程“。协程,有下面两个特点:

    1. 协同。因为是由程序员自己写的调度策略,其通过协作而不是抢占来进行切换
    2. 在用户态完成创建,切换和销毁

    PHP对协程的支持是在迭代生成器的基础上, 增加了可以回送数据给生成器的功能(调用者发送数据给被调用的生成器函数)。 这就把生成器到调用者的单向通信转变为两者之间的双向通信。

    迭代器

    迭代器的概念这里就不赘述了。下面看看我们自己实现的一个迭代器。

     1 class MyIterator implements Iterator
     2 {
     3     private $var = array();
     4 
     5     public function __construct($array)
     6     {
     7         if (is_array($array)) {
     8             $this->var = $array;
     9         }
    10     }
    11 
    12     public function rewind() {   // 第一次迭代时候会执行(或调用该方法的时候),后面的迭代将不会执行。
    13         echo "rewinding
    ";
    14         reset($this->var);  
    15     }
    16 
    17     public function current() {
    18         $var = current($this->var);
    19         echo "current: $var
    ";
    20         return $var;
    21     }
    22 
    23     public function key() {
    24         $var = key($this->var);
    25         echo "key: $var
    ";
    26         return $var;
    27     }
    28 
    29     public function next() {    // 最后执行,就是执行完下面sleep(2)后再执行。(执行了next本次迭代才算结束)
    30         $var = next($this->var);
    31         echo "next: $var
    ";
    32         return $var;
    33     }
    34 
    35     public function valid() {      // 当valid返回false的时候迭代结束
    36         $var = $this->current() !== false;
    37         echo "valid: {$var}
    ";
    38         return $var;
    39     }
    40 }
    41 
    42 $values = array(1,2,3,4);
    43 $it = new MyIterator($values);
    44 
    45 foreach ($it as $a => $b) { // 进行迭代(每次迭代,会依次执行以下方法: rewind(特别之处见上面解释), valid, current, key, next)
    46     print "=====
    ";
    47     sleep(2);
    48 }

    输出:

    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接口,即一个生成器有迭代器的特点)。生成器的实现如下:

     1 function xrange($start, $end, $step = 1) {
     2     for ($i = $start; $i <= $end; $i += $step) {
     3         echo $i . "
    ";
     4         yield;
     5     }
     6 }
     7 
     8 // foreach方式
     9 foreach (xrange(1, 10) as $num) {
    10     
    11 }
    12 
    13 $gene = xrange(1, 10); // gene就是一个生成器对象
    14 // current
    15 $gene->current();  // 打印1
    16 // next
    17 $gene->next();
    18 $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他

    2. send($value)  

    3. current / next...

    yield

    yield的语法很灵活,我们用下面的例子,让大家能明白yield语法的使用。

    用例1: 让出cpu执行权

     1 function task1 () {
     2 for ($i = 1; $i <= 10; ++$i) {
     3         echo "This is task 1 iteration $i.
    ";
     4         yield;// 遇到yield就会主动让出CPU的执行权;
     5     }
     6 }
     7 
     8 $a = task1(); 
     9 $a->current(); // 执行第一次迭代
    10 $a->send(1);  // 唤醒当时让出CPU执行权的yield

    输出:

    This is task 1 iteration 1.
    This is task 1 iteration 2.

    用例2: yield的返回

     1 // yield返回
     2 function task2 () {
     3     for ($i = 1; $i <= 10; ++$i) {
     4             echo "This is task 2 iteration $i.
    ";
     5             yield "lm$i";  // 遇到yield就会主动让出CPU的执行权,for暂停执行, 然后返回"lm"。放在yield后面的值就是返回值
     6         }
     7 }
     8 
     9 $a = task2(); 
    10 $res = $a->current();  // 第一次迭代, 遇到yield返回
    11 var_dump($res);  
    12 $res = $a->send(1);  // 唤醒yield, for继续执行,遇到yield返回。
    13 var_dump($res); 

    输出:

    This is task 2 iteration 1.
    string(3) "lm1"
    This is task 2 iteration 2.
    string(3) "lm2"

    用例3: yield接收值

     1 function task3 () {
     2     for ($i = 1; $i <= 10; ++$i) {
     3             echo "This is task 3 iteration $i.
    ";
     4             $getValue = yield;// 遇到yield就会主动让出CPU的执行权;send后,将send值赋值给getValue
     5             echo $getValue . " ";
     6         }
     7 }
     8 
     9 $a = task3(); 
    10 $a->current();
    11 $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.
    ";
            $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服务器就是靠协程实现异步了实现的。 

    以上内容如果有错误还请留言交流。

  • 相关阅读:
    tomcat调试模式出问题的解决方法
    文本输入 的 onfucus 和 onblur
    how to choose one of compenent and control
    C# Captcha 测试 firefox 和 IE
    谈谈对GridView控件DataKeyName属性的一点认识
    Response.Redirect和Server.Transfer的区别
    select 基本常用语法
    top、postop、scrolltop、scrollHeight、offsetHeight
    onchange 和 onpropertychange区别
    try catch 和if else 语句区别细说
  • 原文地址:https://www.cnblogs.com/xiaoxlm/p/9392249.html
Copyright © 2011-2022 走看看