第7章:突破性能天花板——异步编程与并发处理
章节介绍
想象一下,你的PHP应用正处理一个用户请求,需要向三个不同的外部API获取数据。在传统的同步代码里,你的程序会向第一个API发送请求,然后等待它返回;收到响应后,再向第二个API发送请求,继续等待… 这些等待的累积时间,就是用户感受到的延迟,也是服务器资源(如工作进程或线程)被闲置浪费的时刻。
这就是同步阻塞模式的典型瓶颈,它让PHP在面对I/O密集型任务(如网络请求、文件读写、数据库查询)时显得力不从心。服务器资源被大量用于“等待”,而非“计算”,严重限制了应用的吞吐量和响应速度。
异步编程与并发处理,正是为了打破这个“等待”的枷锁。其核心思想是:当程序需要等待一个耗时的I/O操作完成时,它不会阻塞在原地,而是将这个操作“挂起”,转而去执行其他可以立即执行的任务。等到那个I/O操作完成后,程序再回来处理它的结果。这种方式让单个进程或线程能够同时处理多个任务,极大地提升了资源的利用效率。
在PHP生态中,实现这一目标有多种路径,从语言内置特性到社区强大的库,它们共同构成了突破性能天花板的工具箱。
一种基础且优雅的方式是利用生成器(Generator)实现协作式多任务。生成器函数中的 yield 关键字可以暂停函数的执行,并在之后恢复。这使得我们可以手动调度多个任务,让它们在同一个线程里交替执行。例如,
<?php
function asyncGeneratorTask(string $taskName, int $steps, int $delayMs = 100): Generator
{
for ($i = 1; $i <= $steps; $i++) {
// 模拟该步骤的耗时工作(如I/O、网络请求)
usleep($delayMs * 1000); // 将毫秒转换为微秒
// Yield 返回当前进度,外部调用者可在此处理其他任务
yield [
'task' => $taskName,
'step' => $i,
'total' => $steps,
'progress' => ($i / $steps) * 100
];
}
// 循环结束后,生成器可以返回最终结果
return "任务 '{$taskName}' 已完成";
}
可以创建一个模拟的异步任务,而
<?php
function executeConcurrentGenerators(iterable $generators): array
{
$results = [];
$activeGenerators = [];
// 初始化所有生成器
foreach ($generators as $key => $generator) {
if ($generator instanceof Generator) {
$activeGenerators[$key] = $generator;
}
}
// 主循环:当还有活跃的生成器时继续轮询
while (!empty($activeGenerators)) {
foreach ($activeGenerators as $key => $generator) {
// 推进生成器一步
if ($generator->valid()) {
// 获取当前步骤产生的值(如进度信息),可用于实时输出或记录
$stepInfo = $generator->current();
// 可选:处理或记录 $stepInfo,例如 echo "{$stepInfo['task']}: {$stepInfo['progress']}%\n";
$generator->next(); // 让生成器执行到下一个yield或结束
} else {
// 生成器已完成,获取其返回值
$results[$key] = $generator->getReturn();
// 从活跃列表中移除
unset($activeGenerators[$key]);
}
}
// 可选:在此处可以添加一个微小延迟或调用 usleep(0) 来让出CPU时间片
// usleep(1000); // 1毫秒
}
return $results;
}
则演示了如何简单地轮询调度一组这样的任务,让它们并发推进。这虽然需要我们手动管理“调度”,但它概念清晰,是理解更高级模式的好起点。
更为现代和主流的模式是基于 Promise 的异步编程。Promise 代表一个未来可能完成(或失败)的操作及其最终值。它提供了标准的 then、catch、finally 方法来处理异步结果,将我们从“回调地狱”中解放出来。
<?php
// 首先需要定义Promise类
class Promise {
private $state = 'pending'; // pending, fulfilled, rejected
private $value = null;
private $reason = null;
private $onFulfilledCallbacks = [];
private $onRejectedCallbacks = [];
public function __construct(callable $executor) {
try {
$executor(
function ($value) { $this->resolve($value); },
function ($reason) { $this->reject($reason); }
);
} catch (\Throwable $e) {
$this->reject($e);
}
}
private function resolve($value) {
if ($this->state !== 'pending') return;
$this->state = 'fulfilled';
$this->value = $value;
foreach ($this->onFulfilledCallbacks as $callback) {
$callback($value);
}
// 清空回调数组,释放内存
$this->onFulfilledCallbacks = [];
$this->onRejectedCallbacks = [];
}
private function reject($reason) {
if ($this->state !== 'pending') return;
$this->state = 'rejected';
$this->reason = $reason;
foreach ($this->onRejectedCallbacks as $callback) {
$callback($reason);
}
// 清空回调数组,释放内存
$this->onFulfilledCallbacks = [];
$this->onRejectedCallbacks = [];
}
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): Promise {
return new Promise(function ($resolve, $reject) use ($onFulfilled, $onRejected) {
$handleFulfilled = function ($value) use ($onFulfilled, $resolve, $reject) {
try {
$result = $onFulfilled ? $onFulfilled($value) : $value;
// 如果返回值是Promise,则等待它
if ($result instanceof Promise) {
$result->then($resolve, $reject);
} else {
$resolve($result);
}
} catch (\Throwable $e) {
$reject($e);
}
};
$handleRejected = function ($reason) use ($onRejected, $resolve, $reject) {
try {
if ($onRejected) {
$result = $onRejected($reason);
if ($result instanceof Promise) {
$result->then($resolve, $reject);
} else {
$resolve($result);
}
} else {
$reject($reason);
}
} catch (\Throwable $e) {
$reject($e);
}
};
// 根据当前状态执行不同的处理逻辑
if ($this->state === 'fulfilled') {
// 如果已经完成,立即执行完成回调
$handleFulfilled($this->value);
} elseif ($this->state === 'rejected') {
// 如果已经拒绝,立即执行拒绝回调
$handleRejected($this->reason);
} else {
// 如果还在等待中,将回调函数加入队列
$this->onFulfilledCallbacks[] = $handleFulfilled;
$this->onRejectedCallbacks[] = $handleRejected;
}
});
}
/**
* 添加catch方法用于错误处理
*/
public function catch(callable $onRejected): Promise {
return $this->then(null, $onRejected);
}
/**
* 添加finally方法,无论成功失败都会执行
*/
public function finally(callable $onFinally): Promise {
return $this->then(
function ($value) use ($onFinally) {
$onFinally();
return $value;
},
function ($reason) use ($onFinally) {
$onFinally();
throw $reason;
}
);
}
/**
* 静态方法:创建已完成的Promise
*/
public static function resolve($value): Promise {
return new Promise(function ($resolve) use ($value) {
$resolve($value);
});
}
/**
* 静态方法:创建已拒绝的Promise
*/
public static function reject($reason): Promise {
return new Promise(function ($resolve, $reject) use ($reason) {
$reject($reason);
});
}
}
/**
* 创建一个Promise对象,用于封装一个最终可能完成(resolve)或失败(reject)的异步操作
*
* @param callable $executor 执行器函数,接受两个参数:resolve回调函数和reject回调函数
* @return Promise 返回一个新的Promise实例
*/
function createPromise(callable $executor): Promise {
// 直接创建并返回一个新的Promise实例
return new Promise($executor);
}
展示了如何封装一个异步操作,而
<?php
function asyncHttpRequest(string $url, array $options = []): Promise
{
return new Promise(function ($resolve, $reject) use ($url, $options) {
// 模拟网络延迟
$delayMs = $options['delay'] ?? rand(50, 500);
usleep($delayMs * 1000);
// 模拟随机成功或失败
$successRate = $options['success_rate'] ?? 0.8;
if (mt_rand() / mt_getrandmax() < $successRate) {
// 模拟成功响应
$response = [
'url' => $url,
'status' => 200,
'body' => sprintf('模拟响应数据来自 %s (延迟: %dms)', $url, $delayMs),
'headers' => ['Content-Type' => 'application/json']
];
$resolve($response);
} else {
// 模拟失败响应
$reject(new Exception(sprintf('请求 %s 失败 (模拟)', $url), 500));
}
});
}
则用它模拟了一个常见的HTTP请求场景。更重要的是,我们可以利用
<?php
function runConcurrently(array $promises): Promise
{
// 确保所有元素都是Promise
$promiseArray = [];
foreach ($promises as $key => $value) {
if (!$value instanceof Promise) {
$promiseArray[$key] = Promise::resolve($value);
} else {
$promiseArray[$key] = $value;
}
}
return new Promise(function ($resolve, $reject) use ($promiseArray) {
$results = [];
$completedCount = 0;
$totalCount = count($promiseArray);
// 处理空数组的情况
if ($totalCount === 0) {
$resolve([]);
return;
}
// 为每个Promise添加成功和失败的回调
foreach ($promiseArray as $key => $promise) {
$promise->then(
// 成功回调
function ($value) use ($key, &$results, &$completedCount, $totalCount, $resolve) {
$results[$key] = ['status' => 'fulfilled', 'value' => $value];
$completedCount++;
// 检查是否所有Promise都已完成
if ($completedCount === $totalCount) {
// 按原始顺序排序并提取值
ksort($results);
$finalResults = array_map(function($item) {
return $item['value'];
}, $results);
$resolve($finalResults);
}
},
// 失败回调
function ($reason) use ($key, &$results, &$completedCount, $totalCount, $resolve) {
$results[$key] = ['status' => 'rejected', 'reason' => $reason];
$completedCount++;
// 检查是否所有Promise都已完成
if ($completedCount === $totalCount) {
// 按原始顺序排序并提取失败原因
ksort($results);
$finalResults = array_map(function($item) {
return $item['reason'];
}, $results);
$resolve($finalResults);
}
}
);
}
});
}
来并发执行多个Promise,等待它们全部完成,这正是处理文章开头那个“调用三个API”问题的直接方案。
然而,无限制的并发可能会压垮下游服务或耗尽本地资源。因此,
<?php
function limitConcurrency(iterable $tasks, int $concurrency, callable $taskCreator): Promise
{
return new Promise(function ($resolve, $reject) use ($tasks, $concurrency, $taskCreator) {
$taskIterator = new ArrayIterator(is_array($tasks) ? $tasks : iterator_to_array($tasks));
$results = [];
$pending = 0;
$index = 0;
$hasError = false;
$runNext = function () use (&$runNext, $taskIterator, $taskCreator, &$results, &$pending, &$hasError, $concurrency, $resolve, $reject, &$index) {
// 如果已有错误或没有更多任务且没有 pending 的任务,则结束
while ($pending < $concurrency && $taskIterator->valid() && !$hasError) {
$currentIndex = $index;
$taskData = $taskIterator->current();
$taskIterator->next();
$index++;
$pending++;
$promise = $taskCreator($taskData, $currentIndex);
if (!$promise instanceof Promise) {
$promise = Promise::resolve($promise);
}
$promise->then(
// 任务成功回调
function ($value) use ($currentIndex, &$results, &$pending, &$runNext, &$hasError, $resolve, $reject, $taskIterator) {
$results[$currentIndex] = $value;
$pending--;
$runNext(); // 尝试启动下一个任务
// 如果所有任务都已完成,则解析最终结果
if (!$taskIterator->valid() && $pending === 0) {
$resolve($results);
}
},
// 任务失败回调
function ($reason) use (&$hasError, $reject, &$pending, &$runNext) {
if (!$hasError) {
$hasError = true;
$reject($reason);
}
$pending--;
$runNext(); // 清理pending计数并继续处理剩余任务(如果有)
}
);
}
// 当没有更多任务可启动且没有pending任务时(但没有在while循环中触发resolve),则解析结果
if (!$taskIterator->valid() && $pending === 0 && !$hasError) {
$resolve($results);
}
};
// 初始启动并发任务
$runNext();
});
}
这样的并发控制函数变得至关重要。它可以确保同时执行的任务数量不超过一个上限,是实现稳健、高效并发系统的关键组件。你可以用
<?php
function createMockTaskQueue(int $numberOfTasks, int $minDuration = 10, int $maxDuration = 200): array
{
$tasks = [];
for ($i = 0; $i < $numberOfTasks; $i++) {
$taskId = 'Task_' . ($i + 1);
$duration = rand($minDuration, $maxDuration); // 随机模拟任务处理时间
$tasks[] = [
'id' => $taskId,
'duration_ms' => $duration,
'description' => "模拟任务 #{$taskId},预计耗时 {$duration}ms"
];
}
return $tasks;
}
来生成测试任务,直观地观察不同并发限制下的执行效果。
更进一步,协程(Coroutine)是生成器的强化版,它结合了 Promise,允许我们用近乎同步的代码风格来编写异步逻辑,同时由事件循环自动调度。
<?php
function createSimpleCoroutineScheduler(): Closure
{
$coroutines = new SplQueue(); // 使用队列管理就绪的协程
$isRunning = false;
// 调度器本身是一个函数,可以添加新协程
$scheduler = function (Generator $coroutine = null) use (&$coroutines, &$isRunning) {
static $taskId = 0;
if ($coroutine !== null) {
$taskId++;
// 将新协程加入队列
$coroutines->enqueue(['id' => $taskId, 'coroutine' => $coroutine]);
// echo "调度器: 添加任务 #$taskId\n";
}
// 如果调度器未运行且有任务,则启动运行循环
if (!$isRunning && !$coroutines->isEmpty()) {
$isRunning = true;
// echo "调度器: 开始运行循环\n";
while (!$coroutines->isEmpty()) {
$task = $coroutines->dequeue();
$coroutine = $task['coroutine'];
// 执行协程到下一个 yield 或结束
if ($coroutine->valid()) {
$yieldedValue = $coroutine->current();
// 这里可以处理 yield 出来的值,例如它可能是一个信号或一个子Promise
// 简单场景下,我们直接让协程继续执行下一步
$coroutine->next();
// 将该协程重新放回队列末尾,等待下次调度
$coroutines->enqueue($task);
} else {
// 协程执行完毕,获取返回值
// $returnValue = $coroutine->getReturn();
// echo "调度器: 任务 #{$task['id']} 完成。\n";
}
}
$isRunning = false;
// echo "调度器: 运行循环结束。\n";
}
};
return $scheduler;
}
提供了一个极简的调度器实现,帮助你理解其底层原理。而在生产环境中,你可能会使用 Swoole 或 ReactPHP 等框架提供的高性能、功能完整的协程运行时。
性能优化离不开度量。无论是比较同步与异步模式的差异,还是调整并发数以寻找最佳点,
<?php
function measureExecutionTime(callable $task, int $iterations = 1): array
{
$totalTime = 0;
$peakMemoryStart = memory_get_peak_usage(true);
$results = [];
for ($i = 0; $i < $iterations; $i++) {
$startTime = microtime(true);
$result = $task(); // 执行任务
$endTime = microtime(true);
$executionTime = $endTime - $startTime;
$totalTime += $executionTime;
if ($iterations == 1) {
$results['single_result'] = $result;
}
}
$peakMemoryEnd = memory_get_peak_usage(true);
$memoryUsed = max(0, $peakMemoryEnd - $peakMemoryStart);
return [
'iterations' => $iterations,
'total_time_seconds' => $totalTime,
'average_time_seconds' => $totalTime / $iterations,
'memory_used_bytes' => $memoryUsed,
'memory_used_mb' => round($memoryUsed / 1024 / 1024, 4),
'results' => $results
];
}
都能为你提供精确的执行时间和内存消耗数据,让优化决策有据可依。
最后,异步世界的错误处理不容忽视。由于执行流不再连续,传统的 try-catch 可能无法直接捕获所有异常。
<?php
function handleAsyncErrors(Throwable $error, string $context = 'Async Operation'): void
{
// 记录错误到日志(这里简单输出到error_log,实际项目可使用Monolog等)
$logMessage = sprintf(
"[%s] [%s] %s in %s on line %d\nStack Trace:\n%s",
date('Y-m-d H:i:s'),
$context,
$error->getMessage(),
$error->getFile(),
$error->getLine(),
$error->getTraceAsString()
);
error_log($logMessage);
// 根据运行环境决定处理方式
$env = getenv('APP_ENV') ?: 'production'; // 默认使用生产环境
if ($env === 'local' || $env === 'development') {
// 开发环境:直接抛出异常,便于调试
throw $error;
} else {
// 生产/测试环境:降级处理
// 可以在这里添加降级逻辑,例如:
// 1. 发送警报到监控系统(如Sentry、New Relic)
// 2. 执行备用方案
// 3. 返回默认值(如果调用者需要返回值)
// 注意:由于函数返回void,生产环境下仅记录日志不抛出异常
// 避免因单个异步任务失败导致整个应用崩溃
}
}
提供了一个统一的错误处理入口,建议你在 Promise 链的末端或协程的异常捕获中使用它,来确保系统的健壮性。
本章将带你深入这些技术:从理解为什么需要异步开始,到动手使用生成器和 Promise 构建并发流程,再到掌握高并发控制与协程调度原理,并学会度量和调试异步应用的性能。你的目标不再是写出“能跑”的代码,而是写出能优雅应对高并发、极致利用服务器资源的高性能代码。准备好,我们将一起把PHP应用的性能推向新的高度。
核心概念
在传统的 PHP 脚本中,代码一行接一行地执行,遇到像数据库查询、文件读取或外部 API 调用这样的 I/O 操作时,整个进程就会停下来等待结果返回。这种同步阻塞模型在单个请求中处理多个独立任务时效率很低,因为 CPU 在空闲等待,而时间却在白白流逝。性能调优的一个重要方向,就是让这些等待时间被有效利用起来。
想象一下,你的脚本需要从三个不同的 API 获取数据。在同步模式下,你需要顺序执行三个请求,总时间是它们各自耗时的总和。但如果能让这三个请求同时发出呢?总时间将近似于其中最慢的那个请求的耗时。这就是并发处理带来的性能提升。
PHP 本身并非像 Go 或 Node.js 那样天生异步的语言,但通过一些特性和模式,我们同样可以构建高性能的并发应用。让我们从几个核心的构建块开始。
第一个关键概念是生成器(Generator)和协作式多任务。生成器函数(使用 yield 关键字)可以暂停自身的执行,并在之后从暂停点恢复。这为我们模拟异步行为提供了一种轻量级的手段。例如,
<?php
function asyncGeneratorTask(string $taskName, int $steps, int $delayMs = 100): Generator
{
for ($i = 1; $i <= $steps; $i++) {
// 模拟该步骤的耗时工作(如I/O、网络请求)
usleep($delayMs * 1000); // 将毫秒转换为微秒
// Yield 返回当前进度,外部调用者可在此处理其他任务
yield [
'task' => $taskName,
'step' => $i,
'total' => $steps,
'progress' => ($i / $steps) * 100
];
}
// 循环结束后,生成器可以返回最终结果
return "任务 '{$taskName}' 已完成";
}
函数就创建了一个模拟的长时间任务。它每次 yield 时交出控制权并报告进度,而不是一直执行到结束。这允许我们在一个脚本中“同时”运行多个这样的任务。
但单个生成器没什么用,我们需要一个调度器来管理它们。这就是
<?php
function executeConcurrentGenerators(iterable $generators): array
{
$results = [];
$activeGenerators = [];
// 初始化所有生成器
foreach ($generators as $key => $generator) {
if ($generator instanceof Generator) {
$activeGenerators[$key] = $generator;
}
}
// 主循环:当还有活跃的生成器时继续轮询
while (!empty($activeGenerators)) {
foreach ($activeGenerators as $key => $generator) {
// 推进生成器一步
if ($generator->valid()) {
// 获取当前步骤产生的值(如进度信息),可用于实时输出或记录
$stepInfo = $generator->current();
// 可选:处理或记录 $stepInfo,例如 echo "{$stepInfo['task']}: {$stepInfo['progress']}%\n";
$generator->next(); // 让生成器执行到下一个yield或结束
} else {
// 生成器已完成,获取其返回值
$results[$key] = $generator->getReturn();
// 从活跃列表中移除
unset($activeGenerators[$key]);
}
}
// 可选:在此处可以添加一个微小延迟或调用 usleep(0) 来让出CPU时间片
// usleep(1000); // 1毫秒
}
return $results;
}
函数的作用。它接收一组生成器,并以轮询的方式依次推进每个任务一步。通过这种方式,多个任务就像是在并发执行,尽管在任一时刻只有一个在执行(单线程)。这种模式被称为“协作式”并发,因为它依赖于每个任务主动让出(yield)控制权。这是一种理解异步流程控制的绝佳模型。
然而,手动管理生成器有些繁琐。更现代、更强大的抽象是 Promise。一个 Promise 对象代表一个异步操作的最终完成(或失败)及其结果值。它有三种状态:等待中(pending)、已完成(fulfilled)、已拒绝(rejected)。一旦状态改变,就不会再变。你可以使用
<?php
// 首先需要定义Promise类
class Promise {
private $state = 'pending'; // pending, fulfilled, rejected
private $value = null;
private $reason = null;
private $onFulfilledCallbacks = [];
private $onRejectedCallbacks = [];
public function __construct(callable $executor) {
try {
$executor(
function ($value) { $this->resolve($value); },
function ($reason) { $this->reject($reason); }
);
} catch (\Throwable $e) {
$this->reject($e);
}
}
private function resolve($value) {
if ($this->state !== 'pending') return;
$this->state = 'fulfilled';
$this->value = $value;
foreach ($this->onFulfilledCallbacks as $callback) {
$callback($value);
}
// 清空回调数组,释放内存
$this->onFulfilledCallbacks = [];
$this->onRejectedCallbacks = [];
}
private function reject($reason) {
if ($this->state !== 'pending') return;
$this->state = 'rejected';
$this->reason = $reason;
foreach ($this->onRejectedCallbacks as $callback) {
$callback($reason);
}
// 清空回调数组,释放内存
$this->onFulfilledCallbacks = [];
$this->onRejectedCallbacks = [];
}
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): Promise {
return new Promise(function ($resolve, $reject) use ($onFulfilled, $onRejected) {
$handleFulfilled = function ($value) use ($onFulfilled, $resolve, $reject) {
try {
$result = $onFulfilled ? $onFulfilled($value) : $value;
// 如果返回值是Promise,则等待它
if ($result instanceof Promise) {
$result->then($resolve, $reject);
} else {
$resolve($result);
}
} catch (\Throwable $e) {
$reject($e);
}
};
$handleRejected = function ($reason) use ($onRejected, $resolve, $reject) {
try {
if ($onRejected) {
$result = $onRejected($reason);
if ($result instanceof Promise) {
$result->then($resolve, $reject);
} else {
$resolve($result);
}
} else {
$reject($reason);
}
} catch (\Throwable $e) {
$reject($e);
}
};
// 根据当前状态执行不同的处理逻辑
if ($this->state === 'fulfilled') {
// 如果已经完成,立即执行完成回调
$handleFulfilled($this->value);
} elseif ($this->state === 'rejected') {
// 如果已经拒绝,立即执行拒绝回调
$handleRejected($this->reason);
} else {
// 如果还在等待中,将回调函数加入队列
$this->onFulfilledCallbacks[] = $handleFulfilled;
$this->onRejectedCallbacks[] = $handleRejected;
}
});
}
/**
* 添加catch方法用于错误处理
*/
public function catch(callable $onRejected): Promise {
return $this->then(null, $onRejected);
}
/**
* 添加finally方法,无论成功失败都会执行
*/
public function finally(callable $onFinally): Promise {
return $this->then(
function ($value) use ($onFinally) {
$onFinally();
return $value;
},
function ($reason) use ($onFinally) {
$onFinally();
throw $reason;
}
);
}
/**
* 静态方法:创建已完成的Promise
*/
public static function resolve($value): Promise {
return new Promise(function ($resolve) use ($value) {
$resolve($value);
});
}
/**
* 静态方法:创建已拒绝的Promise
*/
public static function reject($reason): Promise {
return new Promise(function ($resolve, $reject) use ($reason) {
$reject($reason);
});
}
}
/**
* 创建一个Promise对象,用于封装一个最终可能完成(resolve)或失败(reject)的异步操作
*
* @param callable $executor 执行器函数,接受两个参数:resolve回调函数和reject回调函数
* @return Promise 返回一个新的Promise实例
*/
function createPromise(callable $executor): Promise {
// 直接创建并返回一个新的Promise实例
return new Promise($executor);
}
来创建一个 Promise,它接收一个执行器函数,这个函数决定了异步操作何时调用 resolve(成功)或 reject(失败)。
在实际的网络请求中,我们可以用 Promise 来封装。比如,
<?php
function asyncHttpRequest(string $url, array $options = []): Promise
{
return new Promise(function ($resolve, $reject) use ($url, $options) {
// 模拟网络延迟
$delayMs = $options['delay'] ?? rand(50, 500);
usleep($delayMs * 1000);
// 模拟随机成功或失败
$successRate = $options['success_rate'] ?? 0.8;
if (mt_rand() / mt_getrandmax() < $successRate) {
// 模拟成功响应
$response = [
'url' => $url,
'status' => 200,
'body' => sprintf('模拟响应数据来自 %s (延迟: %dms)', $url, $delayMs),
'headers' => ['Content-Type' => 'application/json']
];
$resolve($response);
} else {
// 模拟失败响应
$reject(new Exception(sprintf('请求 %s 失败 (模拟)', $url), 500));
}
});
}
函数就返回一个代表 HTTP 请求结果的 Promise。你无需等待它,可以继续执行其他代码,并通过 .then() 或 await(在协程上下文中)来安排请求完成后的处理逻辑。
单个异步操作很好,但并发处理的威力在于组合多个异步操作。
<?php
function runConcurrently(array $promises): Promise
{
// 确保所有元素都是Promise
$promiseArray = [];
foreach ($promises as $key => $value) {
if (!$value instanceof Promise) {
$promiseArray[$key] = Promise::resolve($value);
} else {
$promiseArray[$key] = $value;
}
}
return new Promise(function ($resolve, $reject) use ($promiseArray) {
$results = [];
$completedCount = 0;
$totalCount = count($promiseArray);
// 处理空数组的情况
if ($totalCount === 0) {
$resolve([]);
return;
}
// 为每个Promise添加成功和失败的回调
foreach ($promiseArray as $key => $promise) {
$promise->then(
// 成功回调
function ($value) use ($key, &$results, &$completedCount, $totalCount, $resolve) {
$results[$key] = ['status' => 'fulfilled', 'value' => $value];
$completedCount++;
// 检查是否所有Promise都已完成
if ($completedCount === $totalCount) {
// 按原始顺序排序并提取值
ksort($results);
$finalResults = array_map(function($item) {
return $item['value'];
}, $results);
$resolve($finalResults);
}
},
// 失败回调
function ($reason) use ($key, &$results, &$completedCount, $totalCount, $resolve) {
$results[$key] = ['status' => 'rejected', 'reason' => $reason];
$completedCount++;
// 检查是否所有Promise都已完成
if ($completedCount === $totalCount) {
// 按原始顺序排序并提取失败原因
ksort($results);
$finalResults = array_map(function($item) {
return $item['reason'];
}, $results);
$resolve($finalResults);
}
}
);
}
});
}
函数正是为此而生。它接收一个 Promise 数组,并返回一个新的 Promise。这个新 Promise 会在所有输入的 Promise 都完成(或其中一个失败)时完成,并汇集所有结果。这样,你就能轻松地发起十个、一百个 HTTP 请求,然后统一等待它们全部返回。
但是,无限制的并发会压垮下游服务或耗尽本地资源(如文件描述符)。因此,并发控制是生产环境中的必备技能。
<?php
function limitConcurrency(iterable $tasks, int $concurrency, callable $taskCreator): Promise
{
return new Promise(function ($resolve, $reject) use ($tasks, $concurrency, $taskCreator) {
$taskIterator = new ArrayIterator(is_array($tasks) ? $tasks : iterator_to_array($tasks));
$results = [];
$pending = 0;
$index = 0;
$hasError = false;
$runNext = function () use (&$runNext, $taskIterator, $taskCreator, &$results, &$pending, &$hasError, $concurrency, $resolve, $reject, &$index) {
// 如果已有错误或没有更多任务且没有 pending 的任务,则结束
while ($pending < $concurrency && $taskIterator->valid() && !$hasError) {
$currentIndex = $index;
$taskData = $taskIterator->current();
$taskIterator->next();
$index++;
$pending++;
$promise = $taskCreator($taskData, $currentIndex);
if (!$promise instanceof Promise) {
$promise = Promise::resolve($promise);
}
$promise->then(
// 任务成功回调
function ($value) use ($currentIndex, &$results, &$pending, &$runNext, &$hasError, $resolve, $reject, $taskIterator) {
$results[$currentIndex] = $value;
$pending--;
$runNext(); // 尝试启动下一个任务
// 如果所有任务都已完成,则解析最终结果
if (!$taskIterator->valid() && $pending === 0) {
$resolve($results);
}
},
// 任务失败回调
function ($reason) use (&$hasError, $reject, &$pending, &$runNext) {
if (!$hasError) {
$hasError = true;
$reject($reason);
}
$pending--;
$runNext(); // 清理pending计数并继续处理剩余任务(如果有)
}
);
}
// 当没有更多任务可启动且没有pending任务时(但没有在while循环中触发resolve),则解析结果
if (!$taskIterator->valid() && $pending === 0 && !$hasError) {
$resolve($results);
}
};
// 初始启动并发任务
$runNext();
});
}
函数演示了如何实现这一点。它允许你设定一个最大并发数,确保同时运行的任务永远不会超过这个限制。任务队列中的任务会按顺序启动,每当一个任务完成,就启动下一个,始终保持活跃任务数等于并发上限。你可以用
<?php
function createMockTaskQueue(int $numberOfTasks, int $minDuration = 10, int $maxDuration = 200): array
{
$tasks = [];
for ($i = 0; $i < $numberOfTasks; $i++) {
$taskId = 'Task_' . ($i + 1);
$duration = rand($minDuration, $maxDuration); // 随机模拟任务处理时间
$tasks[] = [
'id' => $taskId,
'duration_ms' => $duration,
'description' => "模拟任务 #{$taskId},预计耗时 {$duration}ms"
];
}
return $tasks;
}
创建一批模拟任务来测试这个函数的效用。
理解了这些构件,我们来看看背后的引擎:事件循环(Event Loop)和协程调度。虽然 PHP 的标准库没有内置事件循环,但 ReactPHP、Amp 等库实现了它。简单来说,事件循环是一个不断循环的程序,它检查是否有 I/O 事件(如一个 HTTP 响应返回了)、定时器到期或者 Promise 状态改变了,然后执行对应的回调函数。
<?php
function createSimpleCoroutineScheduler(): Closure
{
$coroutines = new SplQueue(); // 使用队列管理就绪的协程
$isRunning = false;
// 调度器本身是一个函数,可以添加新协程
$scheduler = function (Generator $coroutine = null) use (&$coroutines, &$isRunning) {
static $taskId = 0;
if ($coroutine !== null) {
$taskId++;
// 将新协程加入队列
$coroutines->enqueue(['id' => $taskId, 'coroutine' => $coroutine]);
// echo "调度器: 添加任务 #$taskId\n";
}
// 如果调度器未运行且有任务,则启动运行循环
if (!$isRunning && !$coroutines->isEmpty()) {
$isRunning = true;
// echo "调度器: 开始运行循环\n";
while (!$coroutines->isEmpty()) {
$task = $coroutines->dequeue();
$coroutine = $task['coroutine'];
// 执行协程到下一个 yield 或结束
if ($coroutine->valid()) {
$yieldedValue = $coroutine->current();
// 这里可以处理 yield 出来的值,例如它可能是一个信号或一个子Promise
// 简单场景下,我们直接让协程继续执行下一步
$coroutine->next();
// 将该协程重新放回队列末尾,等待下次调度
$coroutines->enqueue($task);
} else {
// 协程执行完毕,获取返回值
// $returnValue = $coroutine->getReturn();
// echo "调度器: 任务 #{$task['id']} 完成。\n";
}
}
$isRunning = false;
// echo "调度器: 运行循环结束。\n";
}
};
return $scheduler;
}
提供了一个极度简化的调度器概念实现,它在一个循环中不断推进各个生成器协程,直到全部完成。这能帮助你理解更复杂库的工作原理。
异步编程中,错误处理路径与同步代码不同,但同样重要。一个在后台失败的 Promise 如果未被处理,可能会导致静默错误。
<?php
function handleAsyncErrors(Throwable $error, string $context = 'Async Operation'): void
{
// 记录错误到日志(这里简单输出到error_log,实际项目可使用Monolog等)
$logMessage = sprintf(
"[%s] [%s] %s in %s on line %d\nStack Trace:\n%s",
date('Y-m-d H:i:s'),
$context,
$error->getMessage(),
$error->getFile(),
$error->getLine(),
$error->getTraceAsString()
);
error_log($logMessage);
// 根据运行环境决定处理方式
$env = getenv('APP_ENV') ?: 'production'; // 默认使用生产环境
if ($env === 'local' || $env === 'development') {
// 开发环境:直接抛出异常,便于调试
throw $error;
} else {
// 生产/测试环境:降级处理
// 可以在这里添加降级逻辑,例如:
// 1. 发送警报到监控系统(如Sentry、New Relic)
// 2. 执行备用方案
// 3. 返回默认值(如果调用者需要返回值)
// 注意:由于函数返回void,生产环境下仅记录日志不抛出异常
// 避免因单个异步任务失败导致整个应用崩溃
}
}
函数提供了一个统一的错误处理入口。最佳实践是在每个 Promise 链的末尾使用 .catch() 方法,或者在使用协程时用 try-catch 包裹 yield 表达式,并将捕获的异常交给这个函数处理,以便进行适当的日志记录和降级。
最后,任何性能优化都必须有度量。你如何证明异步模式确实更快?
<?php
function measureExecutionTime(callable $task, int $iterations = 1): array
{
$totalTime = 0;
$peakMemoryStart = memory_get_peak_usage(true);
$results = [];
for ($i = 0; $i < $iterations; $i++) {
$startTime = microtime(true);
$result = $task(); // 执行任务
$endTime = microtime(true);
$executionTime = $endTime - $startTime;
$totalTime += $executionTime;
if ($iterations == 1) {
$results['single_result'] = $result;
}
}
$peakMemoryEnd = memory_get_peak_usage(true);
$memoryUsed = max(0, $peakMemoryEnd - $peakMemoryStart);
return [
'iterations' => $iterations,
'total_time_seconds' => $totalTime,
'average_time_seconds' => $totalTime / $iterations,
'memory_used_bytes' => $memoryUsed,
'memory_used_mb' => round($memoryUsed / 1024 / 1024, 4),
'results' => $results
];
}
可以帮你进行基准测试。你可以用它分别测量同步顺序执行一批任务和用 runConcurrently 或 limitConcurrency 执行同一批任务的总耗时和内存使用情况。数据会直观地展示并发带来的提升,以及在不同并发数下性能的变化,帮助你找到最佳的配置参数。
将这些概念和工具组合起来,你就能在 PHP 中构建出高效的非阻塞应用。从使用生成器理解协作式任务切换,到利用 Promise 管理复杂的异步依赖,再到通过并发控制保护系统稳定性,最后用严谨的错误处理和性能度量来确保方案的健壮性——这是一个完整的异步编程思维和实践闭环。
实践应用
理解了写作要求,我们直接进入实践部分,看看如何运用这些异步技术来提升PHP应用的性能。
我们从相对容易理解的生成器(Generator)开始。它不仅能用来处理大数据集,更是实现协作式多任务(Cooperative Multitasking)的利器。想象一下,你有一批需要定期清理的日志文件,或者需要向多个用户推送通知,这些任务都很耗时,但并非每一步都需要CPU全力计算,中间可能有I/O等待。用传统的同步方式,只能一个接一个地处理。而协作式多任务允许我们将这些任务“切碎”,让它们在单个线程内交替执行。
例如,我们可以用生成器模拟两个需要多步骤完成的后台任务。`
<?php
function asyncGeneratorTask(string $taskName, int $steps, int $delayMs = 100): Generator
{
for ($i = 1; $i <= $steps; $i++) {
// 模拟该步骤的耗时工作(如I/O、网络请求)
usleep($delayMs * 1000); // 将毫秒转换为微秒
// Yield 返回当前进度,外部调用者可在此处理其他任务
yield [
'task' => $taskName,
'step' => $i,
'total' => $steps,
'progress' => ($i / $steps) * 100
];
}
// 循环结束后,生成器可以返回最终结果
return "任务 '{$taskName}' 已完成";
}
函数就能创建这样的任务。它接受任务名、步骤数和每步的延迟,每次yield返回当前进度。这个yield` 就是“协作”的关键,它主动让出执行权。
那么,谁来管理和调度这些交替执行的任务呢?`
<?php
function executeConcurrentGenerators(iterable $generators): array
{
$results = [];
$activeGenerators = [];
// 初始化所有生成器
foreach ($generators as $key => $generator) {
if ($generator instanceof Generator) {
$activeGenerators[$key] = $generator;
}
}
// 主循环:当还有活跃的生成器时继续轮询
while (!empty($activeGenerators)) {
foreach ($activeGenerators as $key => $generator) {
// 推进生成器一步
if ($generator->valid()) {
// 获取当前步骤产生的值(如进度信息),可用于实时输出或记录
$stepInfo = $generator->current();
// 可选:处理或记录 $stepInfo,例如 echo "{$stepInfo['task']}: {$stepInfo['progress']}%\n";
$generator->next(); // 让生成器执行到下一个yield或结束
} else {
// 生成器已完成,获取其返回值
$results[$key] = $generator->getReturn();
// 从活跃列表中移除
unset($activeGenerators[$key]);
}
}
// 可选:在此处可以添加一个微小延迟或调用 usleep(0) 来让出CPU时间片
// usleep(1000); // 1毫秒
}
return $results;
}
函数演示了这个核心模式。它接收一个生成器数组,通过一个循环不断轮询(send)每个生成器,推动它们向前走一步。如果一个任务还在进行中(生成器有效),就再次放入队列等待下次轮询;如果完成了(生成器返回false`),就收集其结果。这样,所有任务看起来就像在“并发”执行。
下面是一个直观的对比。假设我们有两个任务,每个需要3步,每步耗时100毫秒:
- 同步顺序执行:总时间 ≈ 步骤数 × 任务数 × 单步耗时 = 3 × 2 × 100ms = 600ms。
- 使用生成器协作式并发:由于任务是交替执行的,总时间更接近于 步骤数 × 单步耗时 = 3 × 100ms = 300ms。你可以通过 `
<?php
function measureExecutionTime(callable $task, int $iterations = 1): array
{
$totalTime = 0;
$peakMemoryStart = memory_get_peak_usage(true);
$results = [];
for ($i = 0; $i < $iterations; $i++) {
$startTime = microtime(true);
$result = $task(); // 执行任务
$endTime = microtime(true);
$executionTime = $endTime - $startTime;
$totalTime += $executionTime;
if ($iterations == 1) {
$results['single_result'] = $result;
}
}
$peakMemoryEnd = memory_get_peak_usage(true);
$memoryUsed = max(0, $peakMemoryEnd - $peakMemoryStart);
return [
'iterations' => $iterations,
'total_time_seconds' => $totalTime,
'average_time_seconds' => $totalTime / $iterations,
'memory_used_bytes' => $memoryUsed,
'memory_used_mb' => round($memoryUsed / 1024 / 1024, 4),
'results' => $results
];
}
` 来实际验证这个性能差异。
生成器模式简单直观,非常适合CPU密集型、可分解的任务。但它需要你手动管理调度循环,并且对纯I/O操作的优化有限。这时,我们就需要更强大的抽象:Promise。
Promise代表了一个未来可能完成或失败的操作。`
<?php
// 首先需要定义Promise类
class Promise {
private $state = 'pending'; // pending, fulfilled, rejected
private $value = null;
private $reason = null;
private $onFulfilledCallbacks = [];
private $onRejectedCallbacks = [];
public function __construct(callable $executor) {
try {
$executor(
function ($value) { $this->resolve($value); },
function ($reason) { $this->reject($reason); }
);
} catch (\Throwable $e) {
$this->reject($e);
}
}
private function resolve($value) {
if ($this->state !== 'pending') return;
$this->state = 'fulfilled';
$this->value = $value;
foreach ($this->onFulfilledCallbacks as $callback) {
$callback($value);
}
// 清空回调数组,释放内存
$this->onFulfilledCallbacks = [];
$this->onRejectedCallbacks = [];
}
private function reject($reason) {
if ($this->state !== 'pending') return;
$this->state = 'rejected';
$this->reason = $reason;
foreach ($this->onRejectedCallbacks as $callback) {
$callback($reason);
}
// 清空回调数组,释放内存
$this->onFulfilledCallbacks = [];
$this->onRejectedCallbacks = [];
}
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): Promise {
return new Promise(function ($resolve, $reject) use ($onFulfilled, $onRejected) {
$handleFulfilled = function ($value) use ($onFulfilled, $resolve, $reject) {
try {
$result = $onFulfilled ? $onFulfilled($value) : $value;
// 如果返回值是Promise,则等待它
if ($result instanceof Promise) {
$result->then($resolve, $reject);
} else {
$resolve($result);
}
} catch (\Throwable $e) {
$reject($e);
}
};
$handleRejected = function ($reason) use ($onRejected, $resolve, $reject) {
try {
if ($onRejected) {
$result = $onRejected($reason);
if ($result instanceof Promise) {
$result->then($resolve, $reject);
} else {
$resolve($result);
}
} else {
$reject($reason);
}
} catch (\Throwable $e) {
$reject($e);
}
};
// 根据当前状态执行不同的处理逻辑
if ($this->state === 'fulfilled') {
// 如果已经完成,立即执行完成回调
$handleFulfilled($this->value);
} elseif ($this->state === 'rejected') {
// 如果已经拒绝,立即执行拒绝回调
$handleRejected($this->reason);
} else {
// 如果还在等待中,将回调函数加入队列
$this->onFulfilledCallbacks[] = $handleFulfilled;
$this->onRejectedCallbacks[] = $handleRejected;
}
});
}
/**
* 添加catch方法用于错误处理
*/
public function catch(callable $onRejected): Promise {
return $this->then(null, $onRejected);
}
/**
* 添加finally方法,无论成功失败都会执行
*/
public function finally(callable $onFinally): Promise {
return $this->then(
function ($value) use ($onFinally) {
$onFinally();
return $value;
},
function ($reason) use ($onFinally) {
$onFinally();
throw $reason;
}
);
}
/**
* 静态方法:创建已完成的Promise
*/
public static function resolve($value): Promise {
return new Promise(function ($resolve) use ($value) {
$resolve($value);
});
}
/**
* 静态方法:创建已拒绝的Promise
*/
public static function reject($reason): Promise {
return new Promise(function ($resolve, $reject) use ($reason) {
$reject($reason);
});
}
}
/**
* 创建一个Promise对象,用于封装一个最终可能完成(resolve)或失败(reject)的异步操作
*
* @param callable $executor 执行器函数,接受两个参数:resolve回调函数和reject回调函数
* @return Promise 返回一个新的Promise实例
*/
function createPromise(callable $executor): Promise {
// 直接创建并返回一个新的Promise实例
return new Promise($executor);
}
是它的基础构造。它更符合现代异步编程的思维,通过then、catch、finally 方法来链式组织回调,解决了“回调地狱”的问题。一个常见的应用场景就是并发地发起多个HTTP请求,
<?php
function asyncHttpRequest(string $url, array $options = []): Promise
{
return new Promise(function ($resolve, $reject) use ($url, $options) {
// 模拟网络延迟
$delayMs = $options['delay'] ?? rand(50, 500);
usleep($delayMs * 1000);
// 模拟随机成功或失败
$successRate = $options['success_rate'] ?? 0.8;
if (mt_rand() / mt_getrandmax() < $successRate) {
// 模拟成功响应
$response = [
'url' => $url,
'status' => 200,
'body' => sprintf('模拟响应数据来自 %s (延迟: %dms)', $url, $delayMs),
'headers' => ['Content-Type' => 'application/json']
];
$resolve($response);
} else {
// 模拟失败响应
$reject(new Exception(sprintf('请求 %s 失败 (模拟)', $url), 500));
}
});
}
` 函数模拟了这一点,它返回一个Promise。
真正的威力在于组合这些Promise。`
<?php
function runConcurrently(array $promises): Promise
{
// 确保所有元素都是Promise
$promiseArray = [];
foreach ($promises as $key => $value) {
if (!$value instanceof Promise) {
$promiseArray[$key] = Promise::resolve($value);
} else {
$promiseArray[$key] = $value;
}
}
return new Promise(function ($resolve, $reject) use ($promiseArray) {
$results = [];
$completedCount = 0;
$totalCount = count($promiseArray);
// 处理空数组的情况
if ($totalCount === 0) {
$resolve([]);
return;
}
// 为每个Promise添加成功和失败的回调
foreach ($promiseArray as $key => $promise) {
$promise->then(
// 成功回调
function ($value) use ($key, &$results, &$completedCount, $totalCount, $resolve) {
$results[$key] = ['status' => 'fulfilled', 'value' => $value];
$completedCount++;
// 检查是否所有Promise都已完成
if ($completedCount === $totalCount) {
// 按原始顺序排序并提取值
ksort($results);
$finalResults = array_map(function($item) {
return $item['value'];
}, $results);
$resolve($finalResults);
}
},
// 失败回调
function ($reason) use ($key, &$results, &$completedCount, $totalCount, $resolve) {
$results[$key] = ['status' => 'rejected', 'reason' => $reason];
$completedCount++;
// 检查是否所有Promise都已完成
if ($completedCount === $totalCount) {
// 按原始顺序排序并提取失败原因
ksort($results);
$finalResults = array_map(function($item) {
return $item['reason'];
}, $results);
$resolve($finalResults);
}
}
);
}
});
}
类似于Promise::all`,它接收一组Promise并返回一个新的Promise。这个新Promise会在所有输入Promise完成后完成,结果是一个包含所有结果的数组。这让我们能方便地处理“等待所有子任务完成”的场景。
然而,在现实应用中,无节制地创建并发任务(比如瞬间发起一万个HTTP请求)会导致资源耗尽(内存、端口、对方服务器限制)。并发控制(Concurrency Control) 是高性能异步编程必须考虑的一环。这正是 `
<?php
function limitConcurrency(iterable $tasks, int $concurrency, callable $taskCreator): Promise
{
return new Promise(function ($resolve, $reject) use ($tasks, $concurrency, $taskCreator) {
$taskIterator = new ArrayIterator(is_array($tasks) ? $tasks : iterator_to_array($tasks));
$results = [];
$pending = 0;
$index = 0;
$hasError = false;
$runNext = function () use (&$runNext, $taskIterator, $taskCreator, &$results, &$pending, &$hasError, $concurrency, $resolve, $reject, &$index) {
// 如果已有错误或没有更多任务且没有 pending 的任务,则结束
while ($pending < $concurrency && $taskIterator->valid() && !$hasError) {
$currentIndex = $index;
$taskData = $taskIterator->current();
$taskIterator->next();
$index++;
$pending++;
$promise = $taskCreator($taskData, $currentIndex);
if (!$promise instanceof Promise) {
$promise = Promise::resolve($promise);
}
$promise->then(
// 任务成功回调
function ($value) use ($currentIndex, &$results, &$pending, &$runNext, &$hasError, $resolve, $reject, $taskIterator) {
$results[$currentIndex] = $value;
$pending--;
$runNext(); // 尝试启动下一个任务
// 如果所有任务都已完成,则解析最终结果
if (!$taskIterator->valid() && $pending === 0) {
$resolve($results);
}
},
// 任务失败回调
function ($reason) use (&$hasError, $reject, &$pending, &$runNext) {
if (!$hasError) {
$hasError = true;
$reject($reason);
}
$pending--;
$runNext(); // 清理pending计数并继续处理剩余任务(如果有)
}
);
}
// 当没有更多任务可启动且没有pending任务时(但没有在while循环中触发resolve),则解析结果
if (!$taskIterator->valid() && $pending === 0 && !$hasError) {
$resolve($results);
}
};
// 初始启动并发任务
$runNext();
});
}
函数要解决的问题。它接收一个任务列表、一个最大并发数和一个创建Promise的回调函数。它的内部机制会确保同时运行的Promise数量不超过上限,后续任务会排队等待,直到有“空位”出现。我们常用
<?php
function createMockTaskQueue(int $numberOfTasks, int $minDuration = 10, int $maxDuration = 200): array
{
$tasks = [];
for ($i = 0; $i < $numberOfTasks; $i++) {
$taskId = 'Task_' . ($i + 1);
$duration = rand($minDuration, $maxDuration); // 随机模拟任务处理时间
$tasks[] = [
'id' => $taskId,
'duration_ms' => $duration,
'description' => "模拟任务 #{$taskId},预计耗时 {$duration}ms"
];
}
return $tasks;
}
` 来创建一组模拟任务,测试这个限流函数的有效性。
这些高阶函数(runConcurrently, limitConcurrency)是如何工作的呢?为了理解得更透彻,我们可以看看更底层的调度机制。`
<?php
function createSimpleCoroutineScheduler(): Closure
{
$coroutines = new SplQueue(); // 使用队列管理就绪的协程
$isRunning = false;
// 调度器本身是一个函数,可以添加新协程
$scheduler = function (Generator $coroutine = null) use (&$coroutines, &$isRunning) {
static $taskId = 0;
if ($coroutine !== null) {
$taskId++;
// 将新协程加入队列
$coroutines->enqueue(['id' => $taskId, 'coroutine' => $coroutine]);
// echo "调度器: 添加任务 #$taskId\n";
}
// 如果调度器未运行且有任务,则启动运行循环
if (!$isRunning && !$coroutines->isEmpty()) {
$isRunning = true;
// echo "调度器: 开始运行循环\n";
while (!$coroutines->isEmpty()) {
$task = $coroutines->dequeue();
$coroutine = $task['coroutine'];
// 执行协程到下一个 yield 或结束
if ($coroutine->valid()) {
$yieldedValue = $coroutine->current();
// 这里可以处理 yield 出来的值,例如它可能是一个信号或一个子Promise
// 简单场景下,我们直接让协程继续执行下一步
$coroutine->next();
// 将该协程重新放回队列末尾,等待下次调度
$coroutines->enqueue($task);
} else {
// 协程执行完毕,获取返回值
// $returnValue = $coroutine->getReturn();
// echo "调度器: 任务 #{$task['id']} 完成。\n";
}
}
$isRunning = false;
// echo "调度器: 运行循环结束。\n";
}
};
return $scheduler;
}
提供了一个极简的协程调度器实现。它维护一个协程队列,循环地从队列中取出协程(生成器)并“推进”一步。如果该协程还没完事,就重新放回队尾等待下次调度;如果完成了,就记录结果。这与我们之前executeConcurrentGenerators` 的思路一脉相承,但结构更通用、更清晰,是许多复杂异步框架的雏形。
最后,任何健壮的系统都必须处理失败。异步操作的错误可能发生在未来的任何时刻。`
<?php
function handleAsyncErrors(Throwable $error, string $context = 'Async Operation'): void
{
// 记录错误到日志(这里简单输出到error_log,实际项目可使用Monolog等)
$logMessage = sprintf(
"[%s] [%s] %s in %s on line %d\nStack Trace:\n%s",
date('Y-m-d H:i:s'),
$context,
$error->getMessage(),
$error->getFile(),
$error->getLine(),
$error->getTraceAsString()
);
error_log($logMessage);
// 根据运行环境决定处理方式
$env = getenv('APP_ENV') ?: 'production'; // 默认使用生产环境
if ($env === 'local' || $env === 'development') {
// 开发环境:直接抛出异常,便于调试
throw $error;
} else {
// 生产/测试环境:降级处理
// 可以在这里添加降级逻辑,例如:
// 1. 发送警报到监控系统(如Sentry、New Relic)
// 2. 执行备用方案
// 3. 返回默认值(如果调用者需要返回值)
// 注意:由于函数返回void,生产环境下仅记录日志不抛出异常
// 避免因单个异步任务失败导致整个应用崩溃
}
}
提供了一个统一的错误处理入口。你应该在Promise链的catch方法中,或者在执行生成器的try-catch` 块里调用它。它负责记录日志,并根据环境(生产或开发)决定是抛出异常还是静默降级,确保一个异步任务的失败不会导致整个应用崩溃。
那么,在实践中如何选择呢?如果你的任务逻辑是计算密集且可明确分步的,生成器协作式多任务简单有效。如果你的应用核心是大量I/O操作(如HTTP API调用、数据库查询),尤其是需要精确控制并发数量时,基于Promise的模式配合 limitConcurrency 是更专业的选择。理解 createSimpleCoroutineScheduler 这样的调度原理,则能帮助你在面对更复杂场景时,拥有自己设计和调试的能力。
章节总结
性能调优不只是优化几行代码,更关键的是改变程序执行的方式。当遇到大量I/O等待或需要同时处理多个任务时,传统的同步、顺序执行的模式就会成为瓶颈。异步编程和并发处理正是为了打破这个天花板,让应用在等待数据库查询、响应外部API或读写文件时,依然能够保持活力,去处理其他请求或任务。
生成器提供了一个轻量级的起点,它允许我们将一个耗时的任务分解成多个步骤,并在步骤之间主动让出控制权。这就像是在单线程内实现了协作式的多任务。例如,使用 `
<?php
function asyncGeneratorTask(string $taskName, int $steps, int $delayMs = 100): Generator
{
for ($i = 1; $i <= $steps; $i++) {
// 模拟该步骤的耗时工作(如I/O、网络请求)
usleep($delayMs * 1000); // 将毫秒转换为微秒
// Yield 返回当前进度,外部调用者可在此处理其他任务
yield [
'task' => $taskName,
'step' => $i,
'total' => $steps,
'progress' => ($i / $steps) * 100
];
}
// 循环结束后,生成器可以返回最终结果
return "任务 '{$taskName}' 已完成";
}
可以模拟一个分步完成的任务。而
<?php
function executeConcurrentGenerators(iterable $generators): array
{
$results = [];
$activeGenerators = [];
// 初始化所有生成器
foreach ($generators as $key => $generator) {
if ($generator instanceof Generator) {
$activeGenerators[$key] = $generator;
}
}
// 主循环:当还有活跃的生成器时继续轮询
while (!empty($activeGenerators)) {
foreach ($activeGenerators as $key => $generator) {
// 推进生成器一步
if ($generator->valid()) {
// 获取当前步骤产生的值(如进度信息),可用于实时输出或记录
$stepInfo = $generator->current();
// 可选:处理或记录 $stepInfo,例如 echo "{$stepInfo['task']}: {$stepInfo['progress']}%\n";
$generator->next(); // 让生成器执行到下一个yield或结束
} else {
// 生成器已完成,获取其返回值
$results[$key] = $generator->getReturn();
// 从活跃列表中移除
unset($activeGenerators[$key]);
}
}
// 可选:在此处可以添加一个微小延迟或调用 usleep(0) 来让出CPU时间片
// usleep(1000); // 1毫秒
}
return $results;
}
` 则展示了如何轮流推进多个这样的生成器,让它们在宏观上“同时”取得进展。这种模式非常适合处理那些可以明确分解的批处理任务。
Promise则将异步操作封装成一个未来值的容器,它清晰地定义了成功(resolve)和失败(reject)的状态转移。`
<?php
// 首先需要定义Promise类
class Promise {
private $state = 'pending'; // pending, fulfilled, rejected
private $value = null;
private $reason = null;
private $onFulfilledCallbacks = [];
private $onRejectedCallbacks = [];
public function __construct(callable $executor) {
try {
$executor(
function ($value) { $this->resolve($value); },
function ($reason) { $this->reject($reason); }
);
} catch (\Throwable $e) {
$this->reject($e);
}
}
private function resolve($value) {
if ($this->state !== 'pending') return;
$this->state = 'fulfilled';
$this->value = $value;
foreach ($this->onFulfilledCallbacks as $callback) {
$callback($value);
}
// 清空回调数组,释放内存
$this->onFulfilledCallbacks = [];
$this->onRejectedCallbacks = [];
}
private function reject($reason) {
if ($this->state !== 'pending') return;
$this->state = 'rejected';
$this->reason = $reason;
foreach ($this->onRejectedCallbacks as $callback) {
$callback($reason);
}
// 清空回调数组,释放内存
$this->onFulfilledCallbacks = [];
$this->onRejectedCallbacks = [];
}
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): Promise {
return new Promise(function ($resolve, $reject) use ($onFulfilled, $onRejected) {
$handleFulfilled = function ($value) use ($onFulfilled, $resolve, $reject) {
try {
$result = $onFulfilled ? $onFulfilled($value) : $value;
// 如果返回值是Promise,则等待它
if ($result instanceof Promise) {
$result->then($resolve, $reject);
} else {
$resolve($result);
}
} catch (\Throwable $e) {
$reject($e);
}
};
$handleRejected = function ($reason) use ($onRejected, $resolve, $reject) {
try {
if ($onRejected) {
$result = $onRejected($reason);
if ($result instanceof Promise) {
$result->then($resolve, $reject);
} else {
$resolve($result);
}
} else {
$reject($reason);
}
} catch (\Throwable $e) {
$reject($e);
}
};
// 根据当前状态执行不同的处理逻辑
if ($this->state === 'fulfilled') {
// 如果已经完成,立即执行完成回调
$handleFulfilled($this->value);
} elseif ($this->state === 'rejected') {
// 如果已经拒绝,立即执行拒绝回调
$handleRejected($this->reason);
} else {
// 如果还在等待中,将回调函数加入队列
$this->onFulfilledCallbacks[] = $handleFulfilled;
$this->onRejectedCallbacks[] = $handleRejected;
}
});
}
/**
* 添加catch方法用于错误处理
*/
public function catch(callable $onRejected): Promise {
return $this->then(null, $onRejected);
}
/**
* 添加finally方法,无论成功失败都会执行
*/
public function finally(callable $onFinally): Promise {
return $this->then(
function ($value) use ($onFinally) {
$onFinally();
return $value;
},
function ($reason) use ($onFinally) {
$onFinally();
throw $reason;
}
);
}
/**
* 静态方法:创建已完成的Promise
*/
public static function resolve($value): Promise {
return new Promise(function ($resolve) use ($value) {
$resolve($value);
});
}
/**
* 静态方法:创建已拒绝的Promise
*/
public static function reject($reason): Promise {
return new Promise(function ($resolve, $reject) use ($reason) {
$reject($reason);
});
}
}
/**
* 创建一个Promise对象,用于封装一个最终可能完成(resolve)或失败(reject)的异步操作
*
* @param callable $executor 执行器函数,接受两个参数:resolve回调函数和reject回调函数
* @return Promise 返回一个新的Promise实例
*/
function createPromise(callable $executor): Promise {
// 直接创建并返回一个新的Promise实例
return new Promise($executor);
}
是构建异步逻辑的基础。在处理网络请求时,
<?php
function asyncHttpRequest(string $url, array $options = []): Promise
{
return new Promise(function ($resolve, $reject) use ($url, $options) {
// 模拟网络延迟
$delayMs = $options['delay'] ?? rand(50, 500);
usleep($delayMs * 1000);
// 模拟随机成功或失败
$successRate = $options['success_rate'] ?? 0.8;
if (mt_rand() / mt_getrandmax() < $successRate) {
// 模拟成功响应
$response = [
'url' => $url,
'status' => 200,
'body' => sprintf('模拟响应数据来自 %s (延迟: %dms)', $url, $delayMs),
'headers' => ['Content-Type' => 'application/json']
];
$resolve($response);
} else {
// 模拟失败响应
$reject(new Exception(sprintf('请求 %s 失败 (模拟)', $url), 500));
}
});
}
利用Promise进行封装,使得发起请求后不必原地等待,代码可以继续执行。通过
<?php
function runConcurrently(array $promises): Promise
{
// 确保所有元素都是Promise
$promiseArray = [];
foreach ($promises as $key => $value) {
if (!$value instanceof Promise) {
$promiseArray[$key] = Promise::resolve($value);
} else {
$promiseArray[$key] = $value;
}
}
return new Promise(function ($resolve, $reject) use ($promiseArray) {
$results = [];
$completedCount = 0;
$totalCount = count($promiseArray);
// 处理空数组的情况
if ($totalCount === 0) {
$resolve([]);
return;
}
// 为每个Promise添加成功和失败的回调
foreach ($promiseArray as $key => $promise) {
$promise->then(
// 成功回调
function ($value) use ($key, &$results, &$completedCount, $totalCount, $resolve) {
$results[$key] = ['status' => 'fulfilled', 'value' => $value];
$completedCount++;
// 检查是否所有Promise都已完成
if ($completedCount === $totalCount) {
// 按原始顺序排序并提取值
ksort($results);
$finalResults = array_map(function($item) {
return $item['value'];
}, $results);
$resolve($finalResults);
}
},
// 失败回调
function ($reason) use ($key, &$results, &$completedCount, $totalCount, $resolve) {
$results[$key] = ['status' => 'rejected', 'reason' => $reason];
$completedCount++;
// 检查是否所有Promise都已完成
if ($completedCount === $totalCount) {
// 按原始顺序排序并提取失败原因
ksort($results);
$finalResults = array_map(function($item) {
return $item['reason'];
}, $results);
$resolve($finalResults);
}
}
);
}
});
}
`,我们可以轻松地并行发起多个HTTP请求,并等待所有请求完成,这比一个接一个地串行请求快得多。
然而,无限制的并发可能会压垮下游服务或耗尽本地资源。`
<?php
function limitConcurrency(iterable $tasks, int $concurrency, callable $taskCreator): Promise
{
return new Promise(function ($resolve, $reject) use ($tasks, $concurrency, $taskCreator) {
$taskIterator = new ArrayIterator(is_array($tasks) ? $tasks : iterator_to_array($tasks));
$results = [];
$pending = 0;
$index = 0;
$hasError = false;
$runNext = function () use (&$runNext, $taskIterator, $taskCreator, &$results, &$pending, &$hasError, $concurrency, $resolve, $reject, &$index) {
// 如果已有错误或没有更多任务且没有 pending 的任务,则结束
while ($pending < $concurrency && $taskIterator->valid() && !$hasError) {
$currentIndex = $index;
$taskData = $taskIterator->current();
$taskIterator->next();
$index++;
$pending++;
$promise = $taskCreator($taskData, $currentIndex);
if (!$promise instanceof Promise) {
$promise = Promise::resolve($promise);
}
$promise->then(
// 任务成功回调
function ($value) use ($currentIndex, &$results, &$pending, &$runNext, &$hasError, $resolve, $reject, $taskIterator) {
$results[$currentIndex] = $value;
$pending--;
$runNext(); // 尝试启动下一个任务
// 如果所有任务都已完成,则解析最终结果
if (!$taskIterator->valid() && $pending === 0) {
$resolve($results);
}
},
// 任务失败回调
function ($reason) use (&$hasError, $reject, &$pending, &$runNext) {
if (!$hasError) {
$hasError = true;
$reject($reason);
}
$pending--;
$runNext(); // 清理pending计数并继续处理剩余任务(如果有)
}
);
}
// 当没有更多任务可启动且没有pending任务时(但没有在while循环中触发resolve),则解析结果
if (!$taskIterator->valid() && $pending === 0 && !$hasError) {
$resolve($results);
}
};
// 初始启动并发任务
$runNext();
});
}
函数解决了这个问题,它确保同时执行的任务数不会超过设定值,后续任务会排队等待,从而实现高吞吐下的稳定控制。你可以用
<?php
function createMockTaskQueue(int $numberOfTasks, int $minDuration = 10, int $maxDuration = 200): array
{
$tasks = [];
for ($i = 0; $i < $numberOfTasks; $i++) {
$taskId = 'Task_' . ($i + 1);
$duration = rand($minDuration, $maxDuration); // 随机模拟任务处理时间
$tasks[] = [
'id' => $taskId,
'duration_ms' => $duration,
'description' => "模拟任务 #{$taskId},预计耗时 {$duration}ms"
];
}
return $tasks;
}
` 生成一组测试任务,来观察不同并发限制下的执行效率差异。
为了更深入地理解调度原理,`
<?php
function createSimpleCoroutineScheduler(): Closure
{
$coroutines = new SplQueue(); // 使用队列管理就绪的协程
$isRunning = false;
// 调度器本身是一个函数,可以添加新协程
$scheduler = function (Generator $coroutine = null) use (&$coroutines, &$isRunning) {
static $taskId = 0;
if ($coroutine !== null) {
$taskId++;
// 将新协程加入队列
$coroutines->enqueue(['id' => $taskId, 'coroutine' => $coroutine]);
// echo "调度器: 添加任务 #$taskId\n";
}
// 如果调度器未运行且有任务,则启动运行循环
if (!$isRunning && !$coroutines->isEmpty()) {
$isRunning = true;
// echo "调度器: 开始运行循环\n";
while (!$coroutines->isEmpty()) {
$task = $coroutines->dequeue();
$coroutine = $task['coroutine'];
// 执行协程到下一个 yield 或结束
if ($coroutine->valid()) {
$yieldedValue = $coroutine->current();
// 这里可以处理 yield 出来的值,例如它可能是一个信号或一个子Promise
// 简单场景下,我们直接让协程继续执行下一步
$coroutine->next();
// 将该协程重新放回队列末尾,等待下次调度
$coroutines->enqueue($task);
} else {
// 协程执行完毕,获取返回值
// $returnValue = $coroutine->getReturn();
// echo "调度器: 任务 #{$task['id']} 完成。\n";
}
}
$isRunning = false;
// echo "调度器: 运行循环结束。\n";
}
};
return $scheduler;
}
` 展示了一个极简的调度器是如何工作的。它维护一个协程列表,并循环推动它们执行,直到全部完成。这揭示了异步框架底层“事件循环”或“调度器”的核心思想——管理多个可恢复的执行流程。
在异步世界里,错误处理变得更加重要,因为异常可能在任何一次异步回调或Promise解析中抛出。`
<?php
function handleAsyncErrors(Throwable $error, string $context = 'Async Operation'): void
{
// 记录错误到日志(这里简单输出到error_log,实际项目可使用Monolog等)
$logMessage = sprintf(
"[%s] [%s] %s in %s on line %d\nStack Trace:\n%s",
date('Y-m-d H:i:s'),
$context,
$error->getMessage(),
$error->getFile(),
$error->getLine(),
$error->getTraceAsString()
);
error_log($logMessage);
// 根据运行环境决定处理方式
$env = getenv('APP_ENV') ?: 'production'; // 默认使用生产环境
if ($env === 'local' || $env === 'development') {
// 开发环境:直接抛出异常,便于调试
throw $error;
} else {
// 生产/测试环境:降级处理
// 可以在这里添加降级逻辑,例如:
// 1. 发送警报到监控系统(如Sentry、New Relic)
// 2. 执行备用方案
// 3. 返回默认值(如果调用者需要返回值)
// 注意:由于函数返回void,生产环境下仅记录日志不抛出异常
// 避免因单个异步任务失败导致整个应用崩溃
}
}
` 提供了一个统一的错误处理入口,它可以帮助我们集中记录日志、上报监控或执行降级策略,避免错误被静默吞没。
最后,任何性能优化都需要用数据说话。`
<?php
function measureExecutionTime(callable $task, int $iterations = 1): array
{
$totalTime = 0;
$peakMemoryStart = memory_get_peak_usage(true);
$results = [];
for ($i = 0; $i < $iterations; $i++) {
$startTime = microtime(true);
$result = $task(); // 执行任务
$endTime = microtime(true);
$executionTime = $endTime - $startTime;
$totalTime += $executionTime;
if ($iterations == 1) {
$results['single_result'] = $result;
}
}
$peakMemoryEnd = memory_get_peak_usage(true);
$memoryUsed = max(0, $peakMemoryEnd - $peakMemoryStart);
return [
'iterations' => $iterations,
'total_time_seconds' => $totalTime,
'average_time_seconds' => $totalTime / $iterations,
'memory_used_bytes' => $memoryUsed,
'memory_used_mb' => round($memoryUsed / 1024 / 1024, 4),
'results' => $results
];
}
` 是一个实用的工具,它能帮你定量地对比同步与异步、不同并发数策略之间的性能差异,验证优化是否真正有效。
将这些技术结合起来,就构成了一种新的编程范式:将阻塞的I/O操作异步化,将独立的子任务并发化,并用适当的调度策略和错误处理来保证系统的健壮性。这种转变能显著提升应用的吞吐量和响应能力,尤其是在I/O密集型的场景中,比如微服务调用、数据抓取或实时消息推送。
1873

被折叠的 条评论
为什么被折叠?



