美文网首页
【未完成】这些函数你认识几个?快来鉴定你是初级|中级|还是高级p

【未完成】这些函数你认识几个?快来鉴定你是初级|中级|还是高级p

作者: 张清柏 | 来源:发表于2021-02-04 15:06 被阅读0次

php迭代器

  • 我们先来举一个栗子,我这里有一个文件,大小是136m,
    public function handle(){
        $file=storage_path().'/data/demo.csv';
        $size=filesize($file);
        $size=round($size/(1024*1024),2);
        echo "当前文件大小为{$size}m".PHP_EOL;
    }

运行脚本
zhangguofu@zhangguofudeMacBook-Pro site (test-local) $ php artisan YieldTest
当前文件大小为136.18m

我们写一个方法 (设置最大运行内存128m)读取这个文件


    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle(){
        ini_set('memory_limit', '128M');//我们设置最大运行内存是128m,为了演示效果,小于filesize即可
        $file=storage_path().'/data/demo.csv';

        $res=file_get_contents($file);
        dd($res);
    }

//这个时候运行就会报错了
   Whoops\Exception\ErrorException  : Allowed memory size of 134217728 bytes exhausted (tried to allocate 142802448 bytes)

使用传统按行读取,也会报错,但是这个报错并不是fget报的错误,而是数组超出了内存限制



    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle(){
        ini_set('memory_limit', '128M');//我们设置最大运行内存是128m,为了演示效果,小于filesize即可
        $file=storage_path().'/data/demo.csv';

        $res=$this->readLocalFile($file);
        dd($res);
    }

   /**
     * Notes:加载本地文件,传统方式按行返回
     * User: zhangguofu
     * Date: 2021/1/25
     * Time: 11:36 上午
     * @param $fileName
     * @return mixed
     */
    public function readLocalFile($fileName)
    {
        $handle = fopen($fileName, 'r');
        $lins = [];
        while (!feof($handle)) {
            $lines[] = fgets($handle);
        }
        fclose($handle);
        return $lines;
    }
image.png
  • sleep 的栗子
<?php
function gen1(){
    for ($i=1;$i<11;$i++){
        sleep(1);
       yield $arr[$i]=$i;
    };
}

foreach (gen1() as $v){
    echo $v;
}

  • 那么这时我们就是要处理这个文件,怎么办呢?这个时候我们会不会思考一个问题呢,我用fgets获取数据后,如果不存数组,而是拿到一行数据就返回一行数据,行不行呢?这就是yield的思想

我们来看一下yield官方解释:

生成器函数看起来像普通函数——不同的是普通函数返回一个值,而生成器可以 yield 生成多个想要的值。 任何包含 yield 的函数都是一个生成器函数。

当一个生成器被调用的时候,它返回一个可以被遍历的对象.当你遍历这个对象的时候(例如通过一个foreach循环),PHP 将会在每次需要值的时候调用对象的遍历方法,并在产生一个值之后保存生成器的状态,这样它就可以在需要产生下一个值的时候恢复调用状态。

一旦不再需要产生更多的值,生成器可以简单退出,而调用生成器的代码还可以继续执行,就像一个数组已经被遍历完了。

  • 接下来我们用代码来看一下yield怎么处理业务,这里为了演示,我每次sleep一秒
    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle(){
        ini_set('memory_limit', '128M');//我们设置最大运行内存是128m,为了演示效果,小于filesize即可
        $file=storage_path().'/data/demo.csv';
        $lines = $this->readYieldFile($file);//所有的数据都从生成器里面读取
        foreach ($lines as $row) {
            echo $row;
        }
    }

    /**
     * Notes:使用yield迭代返回文件
     * User: zhangguofu
     * Date: 2021/1/25
     * Time: 11:37 上午
     * @param $fileName
     * @return Generator
     */
    public function readYieldFile($fileName)
    {
        $handle = fopen($fileName, 'r');
        $i=1;
        while (!feof($handle)) {
            sleep(1);
            $line= fgets($handle);
            $i++;
            echo "第{$i}秒".PHP_EOL;
            yield $line;
        }
        fclose($handle);
    }
  • 运行查看结果,发现我们的程序可以正常运行,数据可以读取并处理了


    image.png

到此,总结一下,什么时候可能会用到yield

  • 简单点说,只要是读取大文件的时候都可以用打。如果你工作中也遇到过Allowed memory size of 134217728 bytes exhausted 这类的错误,那么不妨试试yield
  • 使用场景:读取日志文件,分析日志;excel,csv等大文件的数据导入,从数据库读取大量的数据,比如下面这个例子
<?php
function getFruit($conn) {
    $sql = 'SELECT name, color, calories FROM fruit ORDER BY name';
    foreach ($conn->query($sql) as $row) {
        yield($row);
    }
}
?>

这里我们再补充一下yield的常用操作

# http://php.net/manual/zh/class.generator.php
Generator implements Iterator {
    /* Methods */
    //获取迭代器当前值
    public mixed current ( void )
    //获取迭代器当前值
    public mixed getReturn ( void )
    //返回当前产生的键
    public mixed key ( void )
    //生成器从上一次yield处继续执行
    public void next ( void )
    //重置迭代器
    public void rewind ( void )
    //向生成器中传入一个值
    public mixed send ( mixed $value )
    //向生成器中抛入一个异常
    public mixed throw ( Throwable $exception )
    //检查迭代器是否被关闭
    public bool valid ( void )
    //迭代器序列化时执行的方法
    public void __wakeup ( void )
}

不仅于此,yield 还可以生成带key的值

function gen($max)
{
    for ($i=0; $i<$max; $i++) {
        yield $i => $i+1;
    }
    
    return $max;
}

$gen = gen(5);

//var_dump($gen->key());
//var_dump($gen->current());

foreach ($gen as $key=>$val) {
     var_dump($key . "=>" . $val);
}

# output
string(4) "0=>1"
string(4) "1=>2"
string(4) "2=>3"
string(4) "3=>4"
string(4) "4=>5"

  • 这时候是不是感觉有点问题 ,就是 如果yield只是迭代返回值的话,那我们直接把代码写在 第一个循环里面就行了,为什么 还要使用迭代器,并且循环迭代器来获取值呢?这样想也是没有问题的。看很多文章,大多会说yield处理大文件更节省内存,但是真实是这样吗?yield 会节省内存吗?那我们通过栗子来看一下
<?php
function readTheFile($path) {
//    $lines = [];
    $handle = fopen($path, 'r');
    while(!feof($handle)) {
        $lines = trim(fgets($handle));
//        yield $lines;
    }
    fclose($handle);
    echo  $lines;
}
readTheFile('luxun.txt');

function formatBytes($bytes, $precision = 2) {
    $units = array('b', 'kb', 'mb', 'gb', 'tb');
    $bytes = max($bytes, 0);
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
    $pow = min($pow, count($units) - 1);
    $bytes /= (1 << (10 * $pow));
    return round($bytes, $precision) . ' ' . $units[$pow];
}
print formatBytes(memory_get_peak_usage());//
echo PHP_EOL;

输出结果:

zhangguofu@zhangguofudeMacBook-Pro default (master) $ php readFile1.php
424.71 kb

打开yield注释

<?php
function readTheFile($path) {
//    $lines = [];
    $handle = fopen($path, 'r');
    while(!feof($handle)) {
        $lines = trim(fgets($handle));
        yield $lines;
    }
    fclose($handle);
    echo  $lines;
}
readTheFile('luxun.txt');

function formatBytes($bytes, $precision = 2) {
    $units = array('b', 'kb', 'mb', 'gb', 'tb');
    $bytes = max($bytes, 0);
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
    $pow = min($pow, count($units) - 1);
    $bytes /= (1 << (10 * $pow));
    return round($bytes, $precision) . ' ' . $units[$pow];
}
echo "111111".PHP_EOL;
print formatBytes(memory_get_peak_usage());
echo PHP_EOL;

输出结果

zhangguofu@zhangguofudeMacBook-Pro default (master) $ php readFile1.php
111111
424.93 kb

通过上面这个栗子说明什么?

yield并不是节省了你多少内存,而是 传统方式在内存里面使用大数组来存储数据,导致php的内存很大,而 yield 像是一个指针,你可以像指针一样操作它,像current,next等方法。而它也会只生成一条数据在内存里面,下次的迭代是实现了内存中数据的替换。

yield的异步

因为yield 关键字的出现,就是为了让出cpu给下一个程序(协程)使用!我们看一下下面的代码,为了方便观察,我在每行输出都加了时间戳

<?php
echo "#############################";
$time=time();
function gen1($time){
    for ($i = 1; $i <= 10; ++$i) {
        echo PHP_EOL;
        echo "第".(time()-$time)."秒"."----11111----This is task 1 iteration $i.\n";
        yield;
        echo "第".(time()-$time)."秒"."----1111";
        echo PHP_EOL;
        sleep(2);
        echo "第".(time()-$time)."秒"."----1111---sleep完毕";


    }
}
function gen2($time){
    for ($i = 1; $i <= 5; ++$i) {
        echo PHP_EOL;
        echo "第".(time()-$time)."秒"."----22222----This is task 2 iteration $i.\n";
        yield;
        echo "第".(time()-$time)."秒"."----2222";
        echo PHP_EOL;
        sleep(1);
        echo "第".(time()-$time)."秒"."----2222---sleep完毕";

    }
}

//我先来生成两个迭代器
$gen1=gen1($time);
$gen2=gen2($time);
$i=1;
while ($i<11){
    echo "这是第 {$i}次循环";
    echo PHP_EOL;
    var_dump( $gen1->current(),"this is 111111--var_dump--"."第".(time()-$time)."秒");
    var_dump( $gen2->current(),"this is 222222--var_dump--"."第".(time()-$time)."秒");
    $gen1->next();//回复中断
    $gen2->next();//回复中断
    $i++;
}




  • 我们看一下输出结果
zhangguofu@zhangguofudeMacBook-Pro default (master) $ php send2.php
#############################这是第 1次循环

第0秒----11111----This is task 1 iteration 1.
/Users/zhangguofu/website/default/send2.php:52:
NULL
/Users/zhangguofu/website/default/send2.php:52:
string(33) "this is 111111--var_dump--第0秒"

第0秒----22222----This is task 2 iteration 1.
/Users/zhangguofu/website/default/send2.php:53:
NULL
/Users/zhangguofu/website/default/send2.php:53:
string(33) "this is 222222--var_dump--第0秒"
第0秒----1111
第2秒----1111---sleep完毕
第2秒----11111----This is task 1 iteration 2.
第2秒----2222
第3秒----2222---sleep完毕
第3秒----22222----This is task 2 iteration 2.
这是第 2次循环
/Users/zhangguofu/website/default/send2.php:52:
NULL
/Users/zhangguofu/website/default/send2.php:52:
string(33) "this is 111111--var_dump--第3秒"
/Users/zhangguofu/website/default/send2.php:53:
NULL
/Users/zhangguofu/website/default/send2.php:53:
string(33) "this is 222222--var_dump--第3秒"
第3秒----1111
第5秒----1111---sleep完毕
第5秒----11111----This is task 1 iteration 3.
第5秒----2222
第6秒----2222---sleep完毕
第6秒----22222----This is task 2 iteration 3.
这是第 3次循环
/Users/zhangguofu/website/default/send2.php:52:
NULL
/Users/zhangguofu/website/default/send2.php:52:
string(33) "this is 111111--var_dump--第6秒"
/Users/zhangguofu/website/default/send2.php:53:
NULL
/Users/zhangguofu/website/default/send2.php:53:
string(33) "this is 222222--var_dump--第6秒"
第6秒----1111
第8秒----1111---sleep完毕
第8秒----11111----This is task 1 iteration 4.
第8秒----2222
第9秒----2222---sleep完毕
第9秒----22222----This is task 2 iteration 4.
这是第 4次循环
/Users/zhangguofu/website/default/send2.php:52:
NULL
/Users/zhangguofu/website/default/send2.php:52:
string(33) "this is 111111--var_dump--第9秒"
/Users/zhangguofu/website/default/send2.php:53:
NULL
/Users/zhangguofu/website/default/send2.php:53:
string(33) "this is 222222--var_dump--第9秒"
第9秒----1111
第11秒----1111---sleep完毕
第11秒----11111----This is task 1 iteration 5.
第11秒----2222
第12秒----2222---sleep完毕
第12秒----22222----This is task 2 iteration 5.
这是第 5次循环
/Users/zhangguofu/website/default/send2.php:52:
NULL
/Users/zhangguofu/website/default/send2.php:52:
string(34) "this is 111111--var_dump--第12秒"
/Users/zhangguofu/website/default/send2.php:53:
NULL
/Users/zhangguofu/website/default/send2.php:53:
string(34) "this is 222222--var_dump--第12秒"
第12秒----1111
第14秒----1111---sleep完毕
第14秒----11111----This is task 1 iteration 6.
第14秒----2222
第15秒----2222---sleep完毕这是第 6次循环
/Users/zhangguofu/website/default/send2.php:52:
NULL
/Users/zhangguofu/website/default/send2.php:52:
string(34) "this is 111111--var_dump--第15秒"
/Users/zhangguofu/website/default/send2.php:53:
NULL
/Users/zhangguofu/website/default/send2.php:53:
string(34) "this is 222222--var_dump--第15秒"
第15秒----1111
第17秒----1111---sleep完毕
第17秒----11111----This is task 1 iteration 7.
这是第 7次循环
/Users/zhangguofu/website/default/send2.php:52:
NULL
/Users/zhangguofu/website/default/send2.php:52:
string(34) "this is 111111--var_dump--第17秒"
/Users/zhangguofu/website/default/send2.php:53:
NULL
/Users/zhangguofu/website/default/send2.php:53:
string(34) "this is 222222--var_dump--第17秒"
第17秒----1111
第19秒----1111---sleep完毕
第19秒----11111----This is task 1 iteration 8.
这是第 8次循环
/Users/zhangguofu/website/default/send2.php:52:
NULL
/Users/zhangguofu/website/default/send2.php:52:
string(34) "this is 111111--var_dump--第19秒"
/Users/zhangguofu/website/default/send2.php:53:
NULL
/Users/zhangguofu/website/default/send2.php:53:
string(34) "this is 222222--var_dump--第19秒"
第19秒----1111
第21秒----1111---sleep完毕
第21秒----11111----This is task 1 iteration 9.
这是第 9次循环
/Users/zhangguofu/website/default/send2.php:52:
NULL
/Users/zhangguofu/website/default/send2.php:52:
string(34) "this is 111111--var_dump--第21秒"
/Users/zhangguofu/website/default/send2.php:53:
NULL
/Users/zhangguofu/website/default/send2.php:53:
string(34) "this is 222222--var_dump--第21秒"
第21秒----1111
第23秒----1111---sleep完毕
第23秒----11111----This is task 1 iteration 10.
这是第 10次循环
/Users/zhangguofu/website/default/send2.php:52:
NULL
/Users/zhangguofu/website/default/send2.php:52:
string(34) "this is 111111--var_dump--第23秒"
/Users/zhangguofu/website/default/send2.php:53:
NULL
/Users/zhangguofu/website/default/send2.php:53:
string(34) "this is 222222--var_dump--第23秒"
第23秒----1111
第25秒----1111---sleep完毕这是第 11次循环
/Users/zhangguofu/website/default/send2.php:52:
NULL
/Users/zhangguofu/website/default/send2.php:52:
string(34) "this is 111111--var_dump--第25秒"
/Users/zhangguofu/website/default/send2.php:53:
NULL
/Users/zhangguofu/website/default/send2.php:53:
string(34) "this is 222222--var_dump--第25秒"
这是第 12次循环
/Users/zhangguofu/website/default/send2.php:52:
NULL
/Users/zhangguofu/website/default/send2.php:52:
string(34) "this is 111111--var_dump--第25秒"
/Users/zhangguofu/website/default/send2.php:53:
NULL
/Users/zhangguofu/website/default/send2.php:53:
string(34) "this is 222222--var_dump--第25秒"
这是第 13次循环
/Users/zhangguofu/website/default/send2.php:52:
NULL
/Users/zhangguofu/website/default/send2.php:52:
string(34) "this is 111111--var_dump--第25秒"
/Users/zhangguofu/website/default/send2.php:53:
NULL
/Users/zhangguofu/website/default/send2.php:53:
string(34) "this is 222222--var_dump--第25秒"
这是第 14次循环
/Users/zhangguofu/website/default/send2.php:52:
NULL
/Users/zhangguofu/website/default/send2.php:52:
string(34) "this is 111111--var_dump--第25秒"
/Users/zhangguofu/website/default/send2.php:53:
NULL
/Users/zhangguofu/website/default/send2.php:53:
string(34) "this is 222222--var_dump--第25秒"

我们来解释一下 ,为什么会这样执行

/**
 * 我把一个函数分为两个部分, 即yield 前半部分和yield后半部分
 * 在gen1  分为 p1(yield前半部分) 和 n1(yield后半部分)
 * 在gen2分为 p2 和 n2
 *
 *两个var_dump 分别为 d1 和d2 ,那么我们看程序怎么执行的
 *
 * 先来看第1次循环
 * p1 -> d1 -> p2 ->d2 ->n1 ->p1->n2->p2
 *
 * 再来看第2次循环
 * d1->d2->n1->p1->n2->p2
 *
 * 第三次循环
 * d1->d2->n1->p1->n2->p2
 *
 * 只到最后一次循环
 *
 * d1->d2->n1->p1->n2->p2
 *
 *
 */
  • 理清了执行顺序后我们开始分析 为什么 要这么执行
    在第一次执行的时候,p1 执行 遇到yield让出cpu,开始执行dump,然后执行gen2,同样遇到 yield 让出cpu,执行dump,执行完毕后又来了一个next,执行next会发生什么情况呢?之前终端的程序开始执行,于是开始 执行 sleep 操作,执行完sleep 进入下一个for 循环,只到 遇到yield 有开始终端,这就是 p1 d1 p2 d2 n1p1 n2p2的过程,注意,在第一次循环之后,两个程序已经中断执行了,还差yield 后半部分没有执行;

第二次循环的时候,var_dump 和 current 两个操作,按道理是先执行current ,然后dump,但是 这个时候 gen1是让出cpu的,所以先执行了dump,然后 gen里面的程序开始执行。以下类似

  • yield指令提供了任务中断自身的一种方法, 然后把控制交回给任务调度器. 因此协程可以运行多个其他任务. 更进一步来说, yield还可以用来在任务和调度器之间进行通信.比如api阻塞
  • 比如我有一个方法,既要去请求api,还要写日志,那么通常来讲,只能是同步进行了对不对!如果一个接口返回需要5s,那么我写日志 需要在5s 以后,就阻塞在了,但是使用yield之后呢,我们来看一下

简单写一个接口,接口是 睡眠5s


image.png

大家来看下面我写的这个栗子

<?php

// 创建一对cURL资源
$ch1 = curl_init();

// 设置URL和相应的选项
curl_setopt($ch1, CURLOPT_URL, "http://127.0.0.1/api1.php");
curl_setopt($ch1, CURLOPT_HEADER, 0);


// 创建批处理cURL句柄
$mh = curl_multi_init();

// 增加1个句柄
curl_multi_add_handle($mh,$ch1);


// gen1中就是调用三方API,基于multi-curl实现
function gen1( $mh, $ch1 ) {
    do {
        $mrc = curl_multi_exec( $mh, $active );
        // 请求发出后,让出cpu
        $rs = yield;
        echo "收到外部发送数据{$rs}".PHP_EOL;
    } while( $active > 0 );
    $ret = curl_multi_getcontent( $ch1 );
    echo time().PHP_EOL;
    echo $ret.PHP_EOL;
    return false;
}


function gen2() {
        file_put_contents("001",time().PHP_EOL,FILE_APPEND);
        sleep(1);//为了方便看,我sleep了1秒
}

//我先来生成一个迭代器
$gen1=gen1($mh,$ch1);
while (true){
    echo $gen1->current();//阻塞
    gen2();//写文件
    $gen1->send(time());
}

看一下输出,在 请求api 阻塞的时候,我可以继续写我的日志,或者其他操作,然后使用 send 更新 来刷新重启 程序状态


image.png

yield的send 方法

向生成器中传入一个值,并且当做 yield 表达式的结果,然后继续执行生成器。

如果当这个方法被调用时,生成器不在 yield 表达式,那么在传入值之前,它会先运行到第一个 yield 表达式。As such it is not necessary to "prime" PHP generators with a Generator::next() call (like it is done in Python).

就是 说我不仅可以从里面获取值,我还可以往里面发送值,作为yield 表达式

使用php创建子进程

如何实现进程之间内存共享

如何使用套接字完成共享内存

https://www.jianshu.com/p/2751e5d7b259

相关文章

网友评论

      本文标题:【未完成】这些函数你认识几个?快来鉴定你是初级|中级|还是高级p

      本文链接:https://www.haomeiwen.com/subject/vhatzktx.html