美文网首页
【未完成】这些函数你认识几个?快来鉴定你是初级|中级|还是高级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