PHP项目怎么使用Swoole协程?

wen java案例 3

本文目录导读:

PHP项目怎么使用Swoole协程?

  1. 理解Swoole协程的核心概念
  2. 环境搭建
  3. 基础协程示例
  4. 实际项目中的典型场景
  5. 在现有框架中使用Swoole协程
  6. 协程安全注意事项
  7. 生产环境部署建议
  8. 常见问题排查

在PHP项目中使用Swoole协程,主要分为以下几个步骤,我会从基础概念、环境搭建、实际代码示例到生产注意事项,给你一个完整的指南。

理解Swoole协程的核心概念

Swoole协程允许你用同步的代码写法实现异步IO的性能,关键点:

  • 协程是用户态轻量级线程,创建开销极低(约2KB内存)
  • go() 函数创建协程
  • 遇到IO操作(如MySQL查询、Redis读写、HTTP请求)时自动挂起,释放CPU
  • IO完成后自动恢复执行

环境搭建

# 安装Swoole扩展(推荐4.8+或5.x版本)
pecl install swoole
# 或编译安装(更稳定)
phpize && ./configure && make && make install
# 验证安装
php -m | grep swoole
# 必须开启协程钩子(在代码或php.ini中设置)
; php.ini 或代码中
swoole.use_shortname = 'Off'  ; 可选,建议关闭短函数名

重要: Swoole 5.x及以上版本默认开启协程钩子,不需要额外设置。

基础协程示例

1 创建和运行协程

<?php
// 启用协程钩子(Swoole 4.4+)
\Swoole\Runtime::enableCoroutine();
// 创建协程
go(function () {
    echo "Hello from coroutine 1\n";
    // 模拟IO操作
    \Swoole\Coroutine::sleep(1);
    echo "After sleep in coroutine 1\n";
});
go(function () {
    echo "Hello from coroutine 2\n";
    \Swoole\Coroutine::sleep(0.5);
    echo "After sleep in coroutine 2\n";
});
echo "Main process continues...\n";

输出:

Hello from coroutine 1
Hello from coroutine 2
Main process continues...
After sleep in coroutine 2
After sleep in coroutine 1

2 并发执行多个任务

<?php
use Swoole\Coroutine;
use function Swoole\Coroutine\run;
run(function () {
    // 并发执行3个任务
    $tasks = [
        'task1' => function () {
            Coroutine::sleep(2);
            return "Task 1 result";
        },
        'task2' => function () {
            Coroutine::sleep(1);
            return "Task 2 result";
        },
        'task3' => function () {
            Coroutine::sleep(3);
            return "Task 3 result";
        }
    ];
    // 使用WaitGroup等待所有协程完成
    $wg = new Coroutine\WaitGroup();
    $results = [];
    foreach ($tasks as $key => $task) {
        $wg->add();
        go(function () use ($key, $task, $wg, &$results) {
            $results[$key] = $task();
            $wg->done();
        });
    }
    $wg->wait();
    print_r($results);
    // 总耗时约3秒(最长的那个任务),而不是6秒
});

实际项目中的典型场景

1 并发HTTP请求(替代curl_multi)

<?php
use Swoole\Coroutine\Http\Client;
use function Swoole\Coroutine\run;
run(function () {
    $urls = [
        'http://api.example.com/data1',
        'http://api.example.com/data2', 
        'http://api.example.com/data3'
    ];
    $results = [];
    $wg = new \Swoole\Coroutine\WaitGroup();
    foreach ($urls as $index => $url) {
        $wg->add();
        go(function () use ($url, $index, $wg, &$results) {
            $client = new Client(parse_url($url, PHP_URL_HOST), 80);
            $client->get(parse_url($url, PHP_URL_PATH));
            $results[$index] = $client->body;
            $client->close();
            $wg->done();
        });
    }
    $wg->wait();
    print_r($results);
    // 总耗时 = 最慢的请求时间(非累加)
});

2 并发数据库查询(MySQL)

<?php
use Swoole\Coroutine\MySQL;
use function Swoole\Coroutine\run;
run(function () {
    // 创建连接池(生产环境请使用连接池库如easyswoole/pool)
    $pool = new \Swoole\Coroutine\Channel(10);
    // 初始化连接池
    for ($i = 0; $i < 5; $i++) {
        $db = new MySQL();
        $db->connect([
            'host' => '127.0.0.1',
            'port' => 3306,
            'user' => 'root',
            'password' => 'password',
            'database' => 'test',
            'charset' => 'utf8mb4',
        ]);
        $pool->push($db);
    }
    // 并发执行多个查询
    $queries = ['SELECT 1', 'SELECT 2', 'SELECT 3'];
    $wg = new \Swoole\Coroutine\WaitGroup();
    foreach ($queries as $sql) {
        $wg->add();
        go(function () use ($pool, $sql, $wg) {
            $db = $pool->pop();  // 从池中获取连接
            $result = $db->query($sql);
            var_dump($result);
            $pool->push($db);    // 归还连接
            $wg->done();
        });
    }
    $wg->wait();
});

在现有框架中使用Swoole协程

1 结合Laravel/Lumen

// 安装swoole_laravel扩展或自己实现
composer require swooletw/laravel-swoole
// 配置文件 config/swoole_http.php
return [
    'server' => [
        'host' => '0.0.0.0',
        'port' => 1215,
    ],
];
// 启动命令
php artisan swoole:http start
// 在控制器中使用协程
public function index()
{
    $result = \Swoole\Coroutine\run(function () {
        // 协程安全的数据库查询
        $user = User::find(1);  // 自动使用协程MySQL
        $posts = Post::where('user_id', 1)->get();
        // 并发HTTP请求
        $client = new \Swoole\Coroutine\Http\Client('api.example.com', 80);
        $client->get('/data');
        return [
            'user' => $user,
            'posts' => $posts,
            'api_data' => $client->body
        ];
    });
    return response()->json($result);
}

2 结合ThinkPHP

// 安装topthink/think-swoole
composer require topthink/think-swoole
// 启动
php think swoole start
// 控制器中使用
class Index
{
    public function index()
    {
        $result = \Swoole\Coroutine\run(function () {
            // 自动协程化的ORM操作
            $users = User::select();
            return $users;
        });
        return json($result);
    }
}

协程安全注意事项

1 避免全局状态竞争

// ❌ 危险:全局变量在不同协程间共享
$counter = 0;
go(function () use (&$counter) {
    $counter++;  // 可能导致数据竞争
});
// ✅ 安全:使用Channel传递数据
$chan = new \Swoole\Coroutine\Channel();
go(function () use ($chan) {
    $chan->push(1);
});
$result = $chan->pop();

2 使用协程安全的组件

// ❌ 不安全的全局单例
$db = new PDO(...);  // PDO不是协程安全的
// ✅ 安全的做法:每个协程创建独立连接,或使用连接池
go(function () {
    $db = new \Swoole\Coroutine\MySQL();
    $db->connect([...]);
});

3 协程上下文管理

// 使用Context管理请求级别的数据
use Swoole\Coroutine\Context;
// 在协程开始处设置
$context = new Context();
$context['user_id'] = 123;
go(function () use ($context) {
    // 在协程内使用
    $userId = $context['user_id'];
});

生产环境部署建议

1 最佳实践配置

// 创建HTTP服务器
$http = new Swoole\Http\Server("0.0.0.0", 9501);
$http->set([
    'worker_num' => swoole_cpu_num() * 2,  // 根据CPU核心数设置
    'max_request' => 5000,                  // 防止内存泄漏
    'enable_coroutine' => true,             // 开启协程
    'hook_flags' => SWOOLE_HOOK_ALL,        // 全部协程化
]);
$http->on('request', function ($request, $response) {
    // 每个请求自动在协程中运行
    $response->header('Content-Type', 'text/plain');
    $response->end("Hello World\n");
});
$http->start();

2 部署脚本

# 启动
php server.php start
# 守护进程模式
php server.php start -d
# 热重启
kill -USR1 [master_pid]
# 查看状态
ps aux | grep swoole

3 监控指标

// 在代码中获取协程状态
echo "当前协程数: " . \Swoole\Coroutine::stats()['coroutine_num'] . "\n";
echo "Coro总创建数: " . \Swoole\Coroutine::stats()['coroutine_peak_num'] . "\n";

常见问题排查

问题 解决方案
协程栈溢出 增加 stack_size 配置(默认2M)
连接超时 设置 timeout 参数,添加断线重连逻辑
内存泄漏 检查是否所有资源都被释放,使用 max_request 限制
协程死锁 避免在协程间使用互斥锁,优先使用Channel

使用Swoole协程的关键公式:

  1. 启用协程钩子 → 自动将同步IO转为协程IO
  2. 使用 go() 创建任务 → 实现并发
  3. 使用 WaitGroupChannel → 同步协程结果
  4. 使用连接池 → 管理资源和限制并发数
  5. 避免全局状态共享 → 使用Channel传递数据

Swoole协程能让你在保持代码可读性的同时,获得接近Node.js的并发性能,建议从小规模功能开始尝试,逐步替换现有项目中的IO密集操作。

抱歉,评论功能暂时关闭!