概念
咱们知道多进程和多线程是实现并发的有效方式。但多进程的上下文切换资源开销太大;多线程开销相比要小很多,也是现在主流的做法,但其的控制权在内核,从而使用户(程序员)失去了对代码的控制,而且线程的上下文切换也是有一定开销的。 这时为了解决以上问题,"协程"(coroutine)的概念就产生了。你可以将协程理解为更轻量级的线程。这种线程叫做“用户空间线程“。协程,有下面两个特点:
- 协同。因为是由程序员自己写的调度策略,其通过协作而不是抢占来进行切换
- 在用户态完成创建,切换和销毁
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\n"; 14 reset($this->var); 15 } 16 17 public function current() { 18 $var = current($this->var); 19 echo "current: $var\n"; 20 return $var; 21 } 22 23 public function key() { 24 $var = key($this->var); 25 echo "key: $var\n"; 26 return $var; 27 } 28 29 public function next() { // 最后执行,就是执行完下面sleep(2)后再执行。(执行了next本次迭代才算结束) 30 $var = next($this->var); 31 echo "next: $var\n"; 32 return $var; 33 } 34 35 public function valid() { // 当valid返回false的时候迭代结束 36 $var = $this->current() !== false; 37 echo "valid: {$var}\n"; 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 "=====\n"; 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 . "\n"; 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
注意:
生成器不能像函数一样直接调用,调用方法如下:
1. foreach他
2. send($value)
3. current / next...
yield
yield的语法很灵活,我们用下面的例子,让大家能明白yield语法的使用。
用例1: 让出cpu执行权
输出:
This is task 1 iteration 1.
This is task 1 iteration 2.
用例2: yield的返回
输出:
This is task 2 iteration 1. string(3) "lm1" This is task 2 iteration 2. string(3) "lm2"
用例3: yield接收值
输出:
This is task 3 iteration 1.
aa This is task 3 iteration 2.
用例4: yeild接收和返回写在一起
输出:
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服务器就是靠协程实现异步了实现的。
以上内容如果有错误还请留言交流。