Swoole 之进程、内存、协程

1. 进程详解;

什么是进程
进程就是正在运行的程序的一个实例
比如在某个终端里执行一个 PHP 脚本,这时候就相当于开启了一个进程,会有对应的一个进程 id
Swoole 会对进程进行一些管理。参考文档 https://wiki.swoole.com/wiki/page/p-process.html
Swoole 里进程和进程之间是通过 管道来进行通信的。
  • 实例
# 新开一个终端 1:
cd /data/project/test/swoole/demo/
mkdir process
cd process
vim process.php
  • 写入如下内容
<?php

// 创建一个对象,一个子进程
// 第一个参数数回调函数
// 第二参数设置 true,输出内容不会打印屏幕
$process = new swoole_process(function(swoole_process $pro){
    // 执行外部程序,类似 php http_server.php
    $pro->exec("/usr/local/php/bin/php", [__DIR__. '/../server/http_server.php']);
    
}, true);

// 启动子进程,打印子进程 id
$pid = $process->start();
echo $pid . PHP_EOL;

// 当结束的时候,回收子进程
swoole_process::wait();
  • 实例操作
# 终端 1 操作
# process.php 相当于一个**父进程**
php process.php
# 返回子进程号
7632

# 新开一个终端 2:
netstat -anp | grep 8811
# 返回:
tcp        0      0 0.0.0.0:8811            0.0.0.0:*               LISTEN      7632/php
# 或者访问:http://192.168.2.214:8811/

# 进程之间的关系
ps -ef | grep process.php
# 返回
root      7631  3680  0 22:30 pts/1    00:00:00 php process.php
root      7648  4681  0 22:32 pts/2    00:00:00 grep --color=auto process.php
# 第一行的进程,进程号 7631,就是上面 7632 的父进程
# 查看进程数
yum -y install psmisc
pstree -p 7631
# 返回
php(7631)───php(7632)─┬─php(7633)─┬─php(7635)
                      │           ├─php(7636)
                      │           ├─php(7637)
                      │           ├─php(7638)
                      │           ├─php(7639)
                      │           ├─php(7640)
                      │           ├─php(7641)
                      │           └─php(7642)
                      └─{php}(7634)
# 7631 就是 process.php,是父进程
# 7632 就是 process.php 的代码里开启的一个子进程
# 7633 就是代码里开启的 http_server.php,此时 7632 在 Swoole 里对于 7633 而言就是 master 主进程
# 7633 在 Swoole 里就是一个 manager 进程,用来管理 worker 进程和 task 进程,以及分配
# 7633 下还有 7635~7642,就是 worker 进程(worker_num 可以在 http_server.php 实例化对象后 set() 方法里设置)
# $http = new swoole_http_server('0.0.0.0', 8811);
# $http->set([
# 	'worker_num' => 8,
# 	'enable_static_handler' => true,
# 	'document_root'	=> "/data/project/test/swoole/demo/data",
# ]);
# 7634?

ps aft | grep http_server
# 返回
7632 pts/1    Sl+    0:00      \_ /usr/xx/php /data/xx/../http_server.php
7633 pts/1    S+     0:00          \_ /usr/xx/php /data/xx/../http_server.php
7635 pts/1    S+     0:00              \_ /usr/xx/php /data/xx/../http_server.php
7636 pts/1    S+     0:00              \_ /usr/xx/php /data/xx/../http_server.php
7637 pts/1    S+     0:00              \_ /usr/xx/php /data/xx/../http_server.php
7638 pts/1    S+     0:00              \_ /usr/xx/php /data/xx/../http_server.php
7639 pts/1    S+     0:00              \_ /usr/xx/php /data/xx/../http_server.php
7640 pts/1    S+     0:00              \_ /usr/xx/php /data/xx/../http_server.php
7641 pts/1    S+     0:00              \_ /usr/xx/php /data/xx/../http_server.php
7642 pts/1    S+     0:00              \_ /usr/xx/php /data/xx/../http_server.php
7653 pts/0    S+     0:00  \_ grep --color=auto http_server.php

# 总结:
# 启动成功后会创建 master进程 + manager进程 + worker_num 个 worker 进程(1 + 1 + 8 = 10 个)

2. 进程案例解刨;

  • Swoole 进程使用场景
背景
有 10 个 URL 地址,需要获取 10 个 URL 内容并且记录到库里去
原始方案:同步顺序执行
问题
执行慢
解决方案
引入 Swoole process
按需开启 N 个子进程执行
  • 实例
# 新开一个终端 1:
cd /data/project/test/swoole/demo/process
vim curl.php
  • 写入如下内容
<?php

// 开始时间
echo "process-start-time: ".date("Ymd H:i:s") . PHP_EOL;

$workers = [];

$urls = [
    'http://baidu.com',
    'http://sina.com.cn',
    'http://qq.com',
    'http://weibo.com',
    'http://163.com',
    'http://sohu.com'
];

// 传统实现方法
// foreach($urls as $url){
//     $contents[] = file_get_contents($url);
// }

for($i = 0; $i < 6; $i++){
    // 子进程
    $process = new swoole_process(function(swoole_process $worker) use ($i, $urls){
        // curl
        $content = curlData($urls[$i]);
        // 将内容写入管道
        // echo $content . PHP_EOL;
        $worker->write($content . PHP_EOL);
    }, true);
    // 子进程 id
    $pid = $process->start();
    $workers[$pid] = $process;
}

//获取管道内容
foreach($workers as $pro) {
    echo $pro->read();
}

// 模拟请求URL的内容:1s
function curlData($url){
    // curl file_get_contents
    sleep(1);
    return $url . " is finished" . PHP_EOL;
}

// 结束时间
echo "process-end-time: ".date("Ymd H:i:s") . PHP_EOL;
  • 实例操作
php curl.php
# 输出
process-start-time: 20190914 17:21:51
http://baidu.com is finished
http://sina.com.cn is finished
http://qq.com is finished
http://weibo.com is finished
http://163.com is finished
http://sohu.com is finished
process-end-time: 20190914 17:21:52

3. 内存 - table 详解;

内存操作模块
Table
Atomic
Lock
Buffer(即将废弃)
mmap(即将废弃)
channel(即将废弃)
seriable(即将废弃)
Table
Swoole_table 是一个基于共享内存和锁实现的超高性能,并发数据结构
可以用 Swoole_table 在内存中去申请一个内存模块,用来管理相关的数据表
Swoole_table 非常精巧,使用也很方便,性能极高,全内存操作、没有系统调用的 IO 开销
单进程每秒进行写操作 300 万 + 次
参考文档: https://wiki.swoole.com/wiki/page/p-table.html
  • 实例 1:
# 新开一个终端 1:
cd /data/project/test/swoole/demo/
mkdir memory
cd memory
vim table.php
  • 写入如下内容
<?php

// 创建内存表(设置表行数)
$table = new swoole_table(1024);

// 内存表增加列
// 参考:https://wiki.swoole.com/wiki/page/256.html
$table->column('id', $table::TYPE_INT, 4);
$table->column('name', $table::TYPE_STRING, 64);
$table->column('age', $table::TYPE_INT, 3);
// 创建内存表
$table->create();

// 增加一行记录
// 其它的子进程可以共享这些数据
$table->set('key1', ['id' => 1, 'name' => 'Jerry', 'age' => 30]);

// 去内存里获取刚才创建的数据
// 使用场景是:数据共享的时候,多进程操作的时候,
// 比如主进程创建了 10 个子进程,主进程和子进程之间需要共享一份数据
print_R($table->get('key1'));
  • 实例操作 1:
php table.php
# 输出
Array
(
    [id] => 1
    [name] => Jerry
    [age] => 30
)
  • 实例 2:
# 终端 1 修改 table.php
vim table.php
  • 修改
<?php

// 创建内存表(设置表行数)
$table = new swoole_table(1024);

// 内存表增加列
// 参考:https://wiki.swoole.com/wiki/page/256.html
$table->column('id', $table::TYPE_INT, 4);
$table->column('name', $table::TYPE_STRING, 64);
$table->column('age', $table::TYPE_INT, 3);
// 创建内存表
$table->create();

// 增加一行记录
// 其它的子进程可以共享这些数据
// $table->set('key1', ['id' => 1, 'name' => 'Jerry', 'age' => 30]);

// 另外一种方法增加数据
$table['key2'] = [
    'id' => 2, 
    'name' => 'Jack', 
    'age' => 31
];

// 去内存里获取刚才创建的数据
// 使用场景是:数据共享的时候,多进程操作的时候,
// 比如主进程创建了 10 个子进程,主进程和子进程之间需要共享一份数据
// print_R($table->get('key1'));

// 另外一种方法获取数据
// 返回一个对象
print_R($table['key2']);

// 对字段做增加操作
$table->incr('key2', 'age', 2);
echo "incr 2:".PHP_EOL;
print_R($table['key2']);

// 对字段做减操作
$table->decr('key2', 'age', 3);
echo "decr 3:".PHP_EOL;
print_R($table['key2']);

// 删除操作
echo "delete start:".PHP_EOL;
$table->del('key2');
print_r($table['key2']);



// 进程执行完之后,内存里所有的值都会被释放
  • 实例操作 2:
php table.php
# 输出
Swoole\Table\Row Object
(
    [key] => key2
    [value] => Array
        (
            [id] => 2
            [name] => Jack
            [age] => 31
        )

)
incr 2:
Swoole\Table\Row Object
(
    [key] => key2
    [value] => Array
        (
            [id] => 2
            [name] => Jack
            [age] => 33
        )

)
decr 3:
Swoole\Table\Row Object
(
    [key] => key2
    [value] => Array
        (
            [id] => 2
            [name] => Jack
            [age] => 30
        )

)
delete start:
Swoole\Table\Row Object
(
    [key] => key2
    [value] => Array
        (
        )

)

4. 协程。

介绍
Swoole 内置了协程的能力
参考文档: https://wiki.swoole.com/wiki/page/p-coroutine.html
开发者可以无感知的用 同步的代码编写方式达到 异步 IO 的效果和性能,避免了传统异步回调所带来的离散的代码逻辑和陷入多层回调中导致代码无法维护
相关资料
线程、进程、协程的区别
进程、线程、协程与并行、并发
并发与并行的区别
  • 实例 1:
# 新开一个终端 1:
cd /data/project/test/swoole/demo/
mkdir coroutine
cd coroutine
vim redis.php
  • 写入如下内容
<?php

$http = new swoole_http_server('0.0.0.0', 8001);

$http->on('request', function($request, $response) {
    // 获取 redis 里面 的 key 的内容, 然后输出浏览器
    $redis = new Swoole\Coroutine\Redis();

    $redis->connect('127.0.0.1', 6379);
    // redis 密码,没有可忽略
    $redis->auth('asdf');
    $value = $redis->get($request->get['key']);
    
    $response->header("Content-Type", "text/plain");
    $response->end($value);

});

$http->start();
  • 实例操作:
php redis.php
# 浏览器访问 http://192.168.2.214:8001/?key=name
# 返回 redis key 为 name 的值
  • 补充:协程基本使用
<?php
// 定义协程:go 关键字
// 运行 a.php 的时候,相当于运行了一个进程
// go 会在当前进程下开启一个协程
go(function (){
    // co::sleep():模拟 io 等待,会让出控制权,进入协程的调度队列
    // 非阻塞,下面的代码先执行
    // 如果是 sleep(1),那就是阻塞的,按顺序执行
    co::sleep(1);
    echo "协程1" . PHP_EOL;
});

echo "Hello1" . PHP_EOL;

// 开启协程的另外一个方式
Swoole\Coroutine::create( function (){
    echo "协程2" . PHP_EOL;
});

// 2. 如何让 sleep() 效果等同于 co::sleep()
// 需要用到 Swoole 的 runtime 机制
// 可以将 PHP 代码一键协程化
// 以前写 fpm 的 curl、原生的 redis 都可以协程化
// 参考:https://wiki.swoole.com/wiki/page/p-runtime.html

// 睡眠函数:https://wiki.swoole.com/wiki/page/992.html
// 最新的4.2.0版本增加了对sleep函数的Hook,
// 底层替换了sleep、usleep、time_nanosleep、time_sleep_until四个函数。
// 当调用这些睡眠函数时会自动切换为协程定时器调度。不会阻塞进程。
Swoole\Runtime::enableCoroutine(true);
// 此时 sleep() 效果等同于 co::sleep()

// 3. 协程 client,参考 https://wiki.swoole.com/wiki/page/p-coroutine_client.html
// 必须在 go 里运行(必须开启协程)
go(function(){
    $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);
    if(!$client->connect('127.0.0.1', 9501)) {
        echo 'fail:' . $client->errCode . PHP_EOL;
    }else{
        $client->send("send message" . PHP_EOL);
        echo $client->recv();
        $client->close();
    }
});

// 运行:开启 tcp
php ../server/tcp.php
php a.php 
// 返回:Server: 0 - 1 - send message
发布了119 篇原创文章 · 获赞 12 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/hualaoshuan/article/details/100809511