协程基础
概念说明
Viswoole 框架基于 Swoole 协程 提供高性能的并发编程能力。协程(Coroutine)是轻量级的用户态线程,可以在单线程内实现并发执行,避免了传统多线程模型中的上下文切换开销。
框架的 Coroutine 类对 Swoole 协程进行了封装和扩展,提供以下能力:
| 方法 | 说明 |
|---|---|
getContext() | 获取当前协程上下文 |
isCoroutine() | 判断当前是否处于协程环境 |
id() | 获取当前协程 ID |
getTopId() | 获取当前顶级协程 ID |
工作原理
协程与非协程环境兼容
框架同时支持协程和非协程两种运行环境:
- 协程环境(Swoole Server):直接使用 Swoole 原生协程能力
- 非协程环境(CLI / PHPUnit 测试等):通过
mock_context提供兼容层,确保代码在不同环境下行为一致
这种设计使得业务代码无需关心底层是否运行在协程环境中。
协程上下文隔离
这是框架协程设计的核心机制。容器(Container)在协程环境中按 顶级协程 ID 隔离单例:
text
协程 #1 (TopId: 1)
├── Container 单例 A (独立状态)
├── Database 连接池 A
└── Cache 实例 A
协程 #2 (TopId: 2)
├── Container 单例 B (独立状态)
├── Database 连接池 B
└── Cache 实例 B每个顶级协程拥有完全隔离的服务实例集合,彻底避免并发状态竞争问题。
顶级协程的概念
Swoole 协程存在父子关系,子协程继承父协程的上下文。框架使用 顶级协程 ID(即协程树的根节点 ID)作为隔离键,确保同一个请求链路上的所有子协程共享同一组单例。
代码示例
检测当前环境
php
use Viswoole\Core\Coroutine;
if (Coroutine::isCoroutine()) {
echo "当前运行在协程环境中";
echo "协程 ID: " . Coroutine::id();
echo "顶级协程 ID: " . Coroutine::getTopId();
} else {
echo "当前运行在非协程环境中";
}协程上下文的使用
php
use Viswoole\Core\Coroutine;
// 获取当前协程上下文(自动处理非协程环境的兼容)
$context = Coroutine::getContext();
// 在上下文中存取数据(仅当前协程可见)
$context['request_id'] = uniqid();
$context['user_id'] = 1001;
// 后续在同一协程中可读取
$requestId = Coroutine::getContext()['request_id'];容器在协程中的行为
php
// 在不同协程中,同名的 singleton 绑定返回不同的实例
// 协程 A 中
go(function () {
$dbA = app()->make(Database::class);
$dbA->setConnection('conn_A');
});
// 协程 B 中
go(function () {
$dbB = app()->make(Database::class);
// $dbB !== $dbA,两者完全独立
$dbB->getConnection(); // 不会受到协程 A 的影响
});创建协程
php
use Swoole\Coroutine;
// 创建新协程
Coroutine::create(function () {
// 此代码在独立协程中执行
echo "协程 ID: " . Coroutine::getCid();
// 可以嵌套创建子协程
Coroutine::create(function () {
// 子协程,getTopId() 与父协程相同
echo "顶级协程 ID: " . \Viswoole\Core\Coroutine::getTopId();
});
});
echo "主协程继续执行";最佳实践
1. 不要阻塞协程
协程是协作式调度,阻塞操作会挂起当前协程并影响整体性能:
php
// ❌ 不推荐:sleep() 会阻塞整个进程
Coroutine::create(function () {
sleep(5); // 阻塞 5 秒,期间无法处理其他请求
});
// ✅ 推荐:使用 Swoole 的异步睡眠
Coroutine::create(function () {
Swoole\Coroutine::sleep(5); // 只挂起当前协程,不影响其他协程
});2. 理解协程安全的边界
协程上下文隔离保证了框架层面的安全性,但仍需注意共享资源的访问:
php
// ✅ 安全:容器管理的单例自动隔离
$db = app()->make(Database::class); // 每个协程独立实例
// ⚠️ 注意:全局变量、静态属性、文件系统不受协程隔离保护
class Counter
{
private static int $count = 0; // 所有协程共享!
public static function increment(): int
{
return ++self::$count; // 并发不安全
}
}
// 如需跨协程共享计数,使用 Swoole\Atomic 或 Redis 等线程安全方案3. 合理利用协程上下文传递数据
利用协程上下文在请求链路中透传数据,避免参数层层传递:
php
// 中间件中设置
Coroutine::getContext()['trace_id'] = generateTraceId();
Coroutine::getContext()['user'] = $currentUser;
// Controller / Service 中直接读取
$traceId = Coroutine::getContext()['trace_id'];
// 日志中间件自动采集
Log::withContext([
'trace_id' => Coroutine::getContext()['trace_id'],
'coroutine_id' => Coroutine::id(),
]);4. 编写协程兼容的代码
确保代码在协程和非协程环境中都能正常工作:
php
class DataService
{
public function process(): void
{
// ✅ 使用框架提供的 Coroutine 封装,自动兼容两种环境
$context = \Viswoole\Core\Coroutine::getContext();
if ($context !== null) {
$context['start_time'] = microtime(true);
}
// 业务逻辑...
}
}