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

2021-02-04  本文已影响0人  张清柏

php迭代器

    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
<?php
function gen1(){
    for ($i=1;$i<11;$i++){
        sleep(1);
       yield $arr[$i]=$i;
    };
}

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

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

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

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

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

    /**
     * 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);
    }

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

<?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"

<?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
 *
 *
 */

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

简单写一个接口,接口是 睡眠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

上一篇下一篇

猜你喜欢

热点阅读