PHP swoole (4.Memory)

作者: Separes | 来源:发表于2017-06-07 14:47 被阅读357次

    swoole由C实现, 这就意味这它在操作内存的时候具有天然的优势.
    swoole提供的关于内存的操作一共有七类,分别是:

    • 1.Lock: 锁
    • 2.Buffer: 内存对象
    • 3.Table: 内存表
    • 4.Atomic: 原子计数对象
    • 5.mmap: 文件映射
    • 6.Channel: 内存通道
    • 7.Serialize: 序列化 (仅在PHP7及以上版本支持, 鉴于本人使用的是5.6就不再研究)

    1.Lock

    API文档

    swoole提供以下几种锁:

    • 1.文件锁 SWOOLE_FILELOCK
    • 2.读写锁 SWOOLE_RWLOCK
    • 3.信号量 SWOOLE_SEM
    • 4.互斥锁 SWOOLE_MUTEX
    • 5.自旋锁 SWOOLE_SPINLOCK

    关于锁的使用,下次跟flock等一起细说,这里按下不表;

    2.Buffer

    API文档

    Buffer提供了几个操作缓冲区内存的API, 不再赘述, 直接上demo:

    //$size指定了缓冲区内存的初始尺寸。当申请的内存容量不够时swoole底层会自动扩容。
    $i_size = 128;
    $swoole_buffer = new swoole_buffer($i_size);
    
    $s_data = "buffer test demo";
    // 将一个字符串数据追加到缓存区末尾,执行成功后,会返回新的长度
    $swoole_buffer->append($s_data);
    
    // 从缓冲区中取出内容
    $i_offset = 2;
    $i_length = 6;
    $b_remove = false;
    // 三个参数的意义分别为: 偏移量, 读取数据的长度, 从缓冲区的头部将此数据移除
    $s_result = $swoole_buffer->substr($i_offset, $i_length, $b_remove);
    echo ("result1:" . $s_result . PHP_EOL);
    
    // 向缓存区的任意内存位置写数据
    $s_data2 = "insert";
    $swoole_buffer->write($i_offset, $s_data2);
    
    // 验证更改过后的数据
    $s_result2 = $swoole_buffer->read($i_offset, $i_length);
    echo ("result2:" . $s_result2 . PHP_EOL);
    
    // 回收内存
    $swoole_buffer->clear();
    // // 回收内存但不清空缓冲区
    // swoole_buffer->recycle();
    

    需要注意的是:

    • 1.buffer申请的内存无法在进程间共享, 如需共享参考下文table;
    • 2.substr()方法会复制一次内存, 所以只是需要单纯的读取操作应该尽量使用read()方法;
    • 3.substr()方法的remove并不会释放内存空间,只是做了指针偏移, 除非再次手动释放内存:

    The memory is not immediately released, you need to destruct the object in order to really free up memory

    • 4.buffer提供了一个expand()方法手动增加缓冲区的内存尺寸,但是由于当申请的内存容量不够时swoole底层会自动扩容所以个人感觉并没有什么卵用;
    • 5.write()方法属于内存I/O, 将会直接覆写内存,使用的时候必须慎重;
    • 6.recycle()方法也会复制一次内存;

    3.Table

    API文档

    上文中的buffer并不能实现进程间的内存共享,这时就是table的用武之地;
    table基于共享内存和锁实现,理论上内存I/O效率远高于消息队列和管道通信等方式;

    demo:

    // Father进程所引用的回调函数
    function FatherProcessCallback(swoole_process $o_process)
    {
        // 约定创建进程总数
        $i_num_processes = 2;
    
        // 初始化table
        $o_table = new swoole_table(1024);
        $o_table->column('id', swoole_table::TYPE_INT);
        $o_table->column('str', swoole_table::TYPE_STRING, 128);
        // 创建并为table申请内存
        $o_table->create();
        // 设置table内容
        $o_table->set("line1", array("id" => 1, "str" => "string111"));
        $o_table->set("line2", array("id" => 2, "str" => "string222"));
        $o_table->set("line3", array("id" => 3, "str" => "string333"));
    
        for ($i = 0; $i < $i_num_processes; $i++) {
            // 创建Chlidren进程
            $ChildrenProcess = new swoole_process('ChildrenProcessCallback');
    
            // 为Children进程创建table
            $ChildrenProcess->table = $o_table;
            $i_process_id = $ChildrenProcess->start();
        }
    }
    // Children进程所引用的回调函数
    function ChildrenProcessCallback(swoole_process $o_process)
    {
        // 设置等待时间,便于观察
        sleep(2);
    
        // 从table中删除内容
        $o_process->table->del("line3");
        // 从table中读取内容
        $a_table1 = $o_process->table->get("line1");
        $a_table2 = $o_process->table->get("line2");
        // 这里再取line3的话会报 Undefined variable
        var_dump($a_table1);
        var_dump($a_table2);
    }
    
    // 创建Father进程
    $FatherProcess = new swoole_process('FatherProcessCallback');
    $i_process_id = $FatherProcess->start();
    

    运行结果:

    array(2) {
      'id' =>
      int(1)
      'str' =>
      string(9) "string111"
    }
    array(2) {
      'id' =>
      int(2)
      'str' =>
      string(9) "string222"
    }
    array(2) {
      'id' =>
      int(1)
      'str' =>
      string(9) "string111"
    }
    array(2) {
      'id' =>
      int(2)
      'str' =>
      string(9) "string222"
    }
    
    

    需要注意的是:

    • 1.table使用行锁而非全局锁,所以进程抢锁的情况只会发生在并发I/O同一行的情况下;
    • 2.column()方法提供三个静态类型的TYPE, 分别是TYPE_INT(默认4字节), TYPE_STRING, TYPE_FLOAT(8字节);
    • 3.create()方法会申请内存空间,所以必须在子进程之前被调用,否则将不能被正确读取;
    • 4.del()方法不提供整表或批量删除,所以只能for后逐行删除;

    4.Atomic

    所谓原子操作指的是

    所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。
    如果这个操作所处的层(layer)的更高层不能发现其内部实现与结构,那么这个操作是一个原子(atomic)操作。
    原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分。
    将整个操作视作一个整体是原子性的核心特征。

    API文档

    Atomic operation for swoole server.
    It used shared memory and can operate beetween different process. gcc based CPU atomic instructions provided, without locking. Must be created before swoole_server->start in order to be used on the worker process

    这里写了一个不同的进程之间操作计数的demo:

    // Father进程所引用的回调函数
    function FatherProcessCallback(swoole_process $o_process)
    {
        // 约定创建进程总数
        $i_num_processes = 5;
    
        // 创建原子计数对象
        $i_value = 0;
        $swoole_atomic = new swoole_atomic($i_value);
    
        for ($i = 0; $i < $i_num_processes; $i++) {
            // 创建Chlidren进程
            $ChildrenProcess = new swoole_process('ChildrenProcessCallback');
            // 传递atomic计数量
            $ChildrenProcess->atomic = $swoole_atomic;
    
            $i_process_id = $ChildrenProcess->start();
        }
    }
    // Children进程所引用的回调函数
    function ChildrenProcessCallback(swoole_process $o_process)
    {
        // 设置等待时间,便于观察
        sleep(2);
        // 获取atomic并增加计数
        $swoole_atomic = $o_process->atomic;
        $swoole_atomic->add(2);
        $i_atomic = $swoole_atomic->get();
        // 如果值为6,则设置为99
        // 这个方法等同于 if ($i_atomic == 6) {$swoole_atomic->set(99);}
        $swoole_atomic->cmpset(6, 99);
        // set方法
        if ($i_atomic > 100) {
            $swoole_atomic->set(55);
        }
    
        var_dump($i_atomic);
    
    }
    
    // 创建Father进程
    $FatherProcess = new swoole_process('FatherProcessCallback');
    $i_process_id = $FatherProcess->start();
    

    运行结果:

    int(2)
    int(4)
    int(6)
    int(101)
    int(57)
    

    需要注意的是:

    • 1.同上文,atomic必须在start()之前被调用;

    在swoole_server中使用原子计数器,必须在swoole_server->start前创建
    在swoole_process中使用原子计数器,必须在swoole_process->start前创建

    • 2.只能操作32位整数,最大支持42亿;
    • 3.使用add()sub()操作计数时必须是正整数,且小于0或大于42亿的数位将被抛弃;

    5.mmap

    mmap可以减少读写磁盘操作的IO消耗、减少内存拷贝。在实现高性能的磁盘操作程序中,可以使用mmap来提升性能

    demo:

    $p_file = __DIR__ . '/locktest.txt';
    $i_size = 8192;
    if (!is_file($p_file)) {
        file_put_contents($p_file, str_repeat("\0", $i_size));
    }
    
    $fp = swoole\mmap::open($p_file, $i_size);
    
    fwrite($fp, "xxxx");
    fwrite($fp, "sssss");
    
    fflush($fp);
    fclose($fp);
    

    需要注意的是:

    • 使用fflush()将内存中的数据写入到磁盘,但是调用fclose()时会自动执行fflush();

    6.Channel

    同样是传递数据,与Buffer和Table不同的是,Channel的方式更像是一个hash table,这意味着它可以某种程度上的代替json;
    但是实际使用中发现,这种方式在数据和效率方面具有的优势在并不够简洁的操作方法面前也许并不那么大.

    举个栗子,由于stats()方法只能输出通道中的元素总数,所以输出子进程中的所有通道数据需要传递通道数据数量过去进行if循环输出,而使用while则会应为已经try了一次而造成数据丢失,这显然是不合理的.

    不同进程间通过通道传递数据的demo:

    // Father进程所引用的回调函数
    function FatherProcessCallback(swoole_process $o_process)
    {
        // 约定创建进程总数
        $i_num_processes = 5;
    
        // 创建内存通道对象
        $i_size = 1024;
        $o_channel = new Swoole\Channel($i_size);
    
        for ($i = 0; $i < $i_num_processes; $i++) {
            // 创建Chlidren进程
            $ChildrenProcess = new swoole_process('ChildrenProcessCallback');
            // 向通道写入数据
            $o_channel->push(99);
            $o_channel->push($i . "ww");
            $o_channel->push(array('11', 'ss', 'xx'));
            $ChildrenProcess->channel = $o_channel;
    
            $i_process_id = $ChildrenProcess->start();
        }
    }
    // Children进程所引用的回调函数
    function ChildrenProcessCallback(swoole_process $o_process)
    {
        // 设置等待时间,便于观察
        sleep(2);
        // 获取通道数据
        $o_channel = $o_process->channel;
        // 输出所有内容
        for ($i = 0; $i < 3; $i++) {
            $s_content = $o_channel->pop();
            var_dump($s_content);
        }
    
    }
    
    // 创建Father进程
    $FatherProcess = new swoole_process('FatherProcessCallback');
    $i_process_id = $FatherProcess->start();
    

    需要注意的是:

    • 1.底层I/O会自动加锁;
    • 2.可以传递任何形式的数据,但是会在底层被转化为字符串,pop的时候再行还原;
    • 3.传递的数据必须非空,不能为空字符串、空数组、0、null、false等;
    • 4.以本人8G/64bit为例,单个channel能够push()的数量为2720次,

    相关文章

      网友评论

        本文标题:PHP swoole (4.Memory)

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