生成器作用 生成器提供了一种更容易的方法来实现简单的对象迭代 ,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大降低。
生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值 。
一个简单的例子就是使用生成器来重新实现 range() 函数。 标准的 range() 函数需要在内存中生成一个数组包含每一个在它范围内的值,然后返回该数组, 结果就是会产生多个很大的数组。 比如,调用 range(0, 1000000) 将导致内存占用超过 100 MB。
做为一种替代方法, 我们可以实现一个 xrange() 生成器, 只需要足够的内存来创建 Iterator 对象并在内部跟踪生成器的当前状态 ,这样只需要不到1K字节的内存。
实例:将 range() 实现为生成器 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 <?php function xrange($start , $limit , $step = 1) { if ($start < $limit ) { if ($step <= 0) { throw new LogicException('Step must be +ve' ); } for ($i = $start ; $i <= $limit ; $i += $step ) { yield $i ; } } else { if ($step >= 0) { throw new LogicException('Step must be -ve' ); } for ($i = $start ; $i >= $limit ; $i += $step ) { yield $i ; } } } // 注意下面range()和xrange()输出的结果是一样的。 echo 'Single digit odd numbers from range(): ' ;foreach (range(1, 9, 2) as $number ) { echo "$number " ; } echo "\n" ;echo 'Single digit odd numbers from xrange(): ' ;foreach (xrange(1, 9, 2) as $number ) { echo "$number " ; }
以上都会输出:
1 2 Single digit odd numbers from range(): 1 3 5 7 9 Single digit odd numbers from xrange(): 1 3 5 7 9
生成器类对象 Generator 对象是从 generators返回的.
Generator 对象不能通过 new 实例化.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Generator implements Iterator { // 返回当前产生的值 public current ( ) : mixed // 返回当前产生的键 public key ( ) : mixed // 生成器继续执行 public next ( ) : void // 重置迭代器 public rewind ( ) : void // 向生成器中传入一个值 public send ( mixed $value ) : mixed // 向生成器中抛入一个异常 public throw ( Exception $exception ) : void // 检查迭代器是否被关闭 public valid ( ) : bool // 序列化回调 public __wakeup ( ) : void }
斐波那契数列 使用for循环 使用for循环的话,当$n数值大到一定数量,会内存溢出并程序终止。
1 2 3 4 5 6 7 8 9 function fb1($n ) { $arr = []; $arr [0] = 1; $arr [1] = 1; for ($i = 2; $i <= $n ; $i ++) { $arr [$i ] = $arr [$i - 2] + $arr [$i - 1]; } return $arr [$n ]; }
使用递归实现 1 2 3 4 5 6 7 function FibonacciTailRecursive($n , $ret1 , $ret2 ) { if ($n == 1) { return $ret1 ; } return FibonacciTailRecursive($n - 1, $ret2 , $ret1 + $ret2 ); } echo FibonacciTailRecursive(500, 1, 1);
报错了,超过函数最大嵌套级别
1 Fatal error: Uncaught Error: Maximum function nesting level of '256' reached, aborting!
使用yield实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function fib($n ){ $cur = 1; $prev = 0; for ($i = 0; $i < $n ; $i ++) { yield $cur ; $temp = $cur ; $cur = $prev + $cur ; $prev = $temp ; } } $fibs = fib(500);foreach ($fibs as $fib ) { echo " " . $fib ; }
其他语法 yield表达式也可以赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function gen(){ while (true ) { $data = yield; echo $data ."<br>" ; echo $data ."<br>" ; echo "test<br>" ; } echo "test2" ; return "返回值" ; } $obj = gen();var_dump($obj ); for ($i = 1; $i < 7; $i ++) { $obj ->send($i ); }
生成器特性总结
yield是生成器所需要的关键字,必须在函数内部,有yield的函数叫做”生成器函数”
调用生成器函数时,函数将返回一个继承了Iterator的生成器
yield作为表达式使用时,可将一个值加入到生成器中进行遍历,遍历完会中断下面的语句运行,并且保存状态,当下次遍历时会继续执行(这就是while(true)没有造成阻塞的原因)
当send传入参数时,yield可作为一个变量使用,这个变量等于传入的参数
使用协同程序实现合作多任务 多任务协作这个术语中的“协作”说明了如何进行这种切换的:它要求当前正在运行的任务自动把控制传回给调度器,这样它就可以运行其他任务了。这与“抢占”多任务相反,抢占多任务是这样的:调度器可以中断运行了一段时间的任务,不管它喜欢还是不喜欢。协作多任务在Windows的早期版本(windows95)和Mac OS中有使用,不过它们后来都切换到使用抢先多任务了。理由相当明确:如果你依靠程序自动传回 控制的话,那么坏行为的软件将很容易为自身占用整个CPU,不与其他任务共享。
这个时候你应当明白协程和任务调度之间的联系:yield指令提供了任务中断自身的一种方法,然后把控制传递给调度器。因此协程可以运行多个其他任务。更进一步来说,yield可以用来在任务和调度器之间进行通信。
我们的目的是 对 “任务 ”用更轻量级的包装的协程函数:
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 <?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(); } }
调度器 现在不得不比多任务循环要做稍微多点了,然后才运行多任务:
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 <?php require 'Task.php' ; 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 ); } } } }
newTask()方法(使用下一个空闲的任务id)创建一个新任务,然后把这个任务放入任务映射数组里。接着它通过把任务放入任务队列里来实现对任务的调度。接着run()方法扫描任务队列,运行任务。如果一个任务结束了,那么它将从队列里删除,否则它将在队列的末尾再次被调度。
让我们看看下面具有两个简单(并且没有什么意义)任务的调度器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php require 'Scheduler.php' ; 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();
两个任务都仅仅回显一条信息,然后使用yield把控制回传给调度器。输出结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 This is task 1 iteration 1. This is task 2 iteration 1. This is task 1 iteration 2. This is task 2 iteration 2. This is task 1 iteration 3. This is task 2 iteration 3. This is task 1 iteration 4. This is task 2 iteration 4. This is task 1 iteration 5. This is task 2 iteration 5. This is task 1 iteration 6. This is task 1 iteration 7. This is task 1 iteration 8. This is task 1 iteration 9. This is task 1 iteration 10.