PHP swoole (4.Memory)

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次,
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容