PHP应用性能调优与深度优化实战指南-7

第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 代表一个未来可能完成(或失败)的操作及其最终值。它提供了标准的 thencatchfinally 方法来处理异步结果,将我们从“回调地狱”中解放出来。

<?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
    ];
}

可以帮你进行基准测试。你可以用它分别测量同步顺序执行一批任务和用 runConcurrentlylimitConcurrency 执行同一批任务的总耗时和内存使用情况。数据会直观地展示并发带来的提升,以及在不同并发数下性能的变化,帮助你找到最佳的配置参数。

将这些概念和工具组合起来,你就能在 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);
}

是它的基础构造。它更符合现代异步编程的思维,通过thencatchfinally 方法来链式组织回调,解决了“回调地狱”的问题。一个常见的应用场景就是并发地发起多个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密集型的场景中,比如微服务调用、数据抓取或实时消息推送。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

霸王大陆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值