PHP 5.5开始新增了神奇的关键字yield,能够从生成器(generators)中返回数据。yield有点像普通函数中的关键字return,但是不会彻底停止函数的执行(普通函数一旦return便不执行了),可以暂停循环并返回值,每一次调用便从中断处继续迭代。生成器可以用于替代循环迭代,每一次调用返回一个生成器对象(generator)。
yield能够延迟执行,可以用于对大量数据进行迭代而不用预先在内存中生成数组。例如动态生成一个大数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?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" ; } $range = xrange(1, 1000000); var_dump( $range ); // object(Generator)#1 var_dump( $range instanceof Iterator); // bool(true) |
利用yield简便、高效的生成fibonacci数列而不是循环或递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?php function fibonacci( $count ) { $prev = 0; $current = 1; for ( $i = 0; $i < $count ; ++ $i ) { yield $prev ; $next = $prev + $current ; $prev = $current ; $current = $next ; } } foreach (fibonacci(48) as $i => $value ) { echo $i , ' -> ' , $value , PHP_EOL; } |
利用yield来循环读取文件,而不需要像file函数那样一次性加载进来,节省内存
1 2 3 4 5 6 7 8 9 10 11 12 | <?php function file_lines( $filename ) { $file = fopen ( $filename , 'r' ); while (( $line = fgets ( $file )) !== false) { yield $line ; } fclose( $file ); } foreach (file_lines( 'somefile' ) as $line ) { // do some work here } |
yield除了能够返回值,用作变量时还可以接收值。
1 2 3 4 5 6 7 8 9 10 11 | <?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具有中断执行后再次调用又可以从中断处执行,外界又可以通过生成器对象(generator)的send方法进行交互,可以用于协程(coroutine),作多任务协作的流程控制。这里有个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | <?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(); } } 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 ); } } } } 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(); |
有些任务是需要交互进行的,如socket的监听和回复;有些任务异步执行又需要回调,如A执行不阻塞程序,但B执行又取决于A是否执行完毕。这些都可以使用yield来进行封装,达到流程控制的目的。
参考链接:
Generators overview
What does yield mean in PHP?
What is the difference between a generator and an array?
Cooperative multitasking using coroutines (in PHP!)
What Generators Can Do For You
Generators and Coroutines in PHP
Generators in PHP
协程与yield
http://www.hitoy.org/coroutine-and-yield.html
Co-operative PHP Multitasking
Generator (computer programming)
异步处理在分布式系统中的优化作用