thinkphp 异步执行 并发执行server方法,pcntl_signal()

1.实现server 类名

<?php
declare (strict_types = 1);

namespace app\service;

use think\Exception;
use think\facade\Cache;
use think\facade\Db;
use think\facade\Log;
use think\App;

class AsyncService
{
    protected $maxProcesses = 10; // 最大进程数
    protected $processes = []; // 存储子进程PID
    protected $maxTotalProcesses = 20; // 最大总进程数
    protected $processTimeout = 300; // 进程超时时间(秒)
    protected $queueKey = 'async_task_queue'; // 缓存队列键名
    protected $isProcessing = false; // 是否正在处理队列
    protected $app; // 应用实例
    protected $concurrentTasks = []; // 并发任务组

    public function __construct()
    {
        $this->app = app();
    }

    /**
     * 多进程异步执行
     * @param array $tasks 任务数组,每个任务格式为 [对象类名, 方法名, 参数数组]
     * @return void
     */
    public function multiProcess(array $tasks)
    {
        try {
            if (!extension_loaded('pcntl')) {
                throw new \RuntimeException('需要安装并启用 pcntl 扩展');
            }
          
            // 清理已结束的进程
            $this->cleanup();

            // 将任务组加入并发任务列表
            $this->concurrentTasks = array_merge($this->concurrentTasks, $tasks);

            // 如果没有正在处理队列,则开始处理
            if (!$this->isProcessing) {
                $this->processConcurrentTasks();
            }
        } catch (\Throwable $e) {
            Log::write('multiProcess 执行错误: ' . $e->getMessage() . "\n" . $e->getTraceAsString());
            throw $e;
        }
    }

    /**
     * 处理并发任务
     */
    protected function processConcurrentTasks()
    {
        try {
            $this->isProcessing = true;
          
            while (true) {
                // 获取当前空闲进程数
                $currentProcesses = $this->getProcessCount();
                $availableProcesses = $this->maxTotalProcesses - $currentProcesses;
                
                if ($availableProcesses <= 0) {
                    Log::info('没有空闲进程,等待进程释放');
                    sleep(1);
                    $this->cleanup();
                    continue;
                }

                // 检查是否还有待处理的任务
                if (empty($this->concurrentTasks)) {
                    Log::info('所有并发任务已处理完成');
                    $this->isProcessing = false;
                    break;
                }
                
                // 计算本次要处理的任务数
                $processCount = min($availableProcesses, count($this->concurrentTasks), $this->maxProcesses);
             
                // 取出要处理的任务
                $tasksToProcess = array_splice($this->concurrentTasks, 0, $processCount);
                // 并发执行任务
                foreach ($tasksToProcess as $task) {
               
                    $this->forkProcess([
                        'class' => $task[0],
                        'method' => $task[1],
                        'params' => $task[2] ?? []
                    ]);
                }
                // 等待一段时间再检查
                sleep(1);
                $this->cleanup();
            }
        } catch (\Throwable $e) {
            Log::write('processConcurrentTasks 执行错误: ' . $e->getMessage() . "\n" . $e->getTraceAsString());
            $this->isProcessing = false;
            throw $e;
        }
    }

    /**
     * 创建子进程执行任务
     * @param array $task
     */
    protected function forkProcess(array $task)
    {
        try {
            $pid = pcntl_fork();
            
            if ($pid === -1) {
                throw new \RuntimeException('无法 fork 进程');
            }
            
            if ($pid === 0) {
                // 子进程
                try {
                    // 设置进程标题
                    if (function_exists('cli_set_process_title')) {
                        cli_set_process_title('php async worker');
                    }
                    
                    // 设置超时处理
                    pcntl_signal(SIGALRM, function() {
                        Log::write('进程执行超时');
                        exit(1);
                    });
                    pcntl_alarm($this->processTimeout);
                    
                    // 执行任务
                    $class = $task['class'];
                    $method = $task['method'];
                    $params = $task['params'];
                    
                   
                    
                    // 在子进程中重新初始化应用
                    $app = new App();
                    $app->initialize();
                    
                    // 使用应用实例创建控制器
                    $instance = $app->make($class);
                    $result = call_user_func_array([$instance, $method], $params);
                    
                    
                    
                    // 正常退出
                    exit(0);
                } catch (\Throwable $e) {
                    Log::write('子进程执行错误: ' . $e->getMessage() . "\n" . $e->getTraceAsString());
                    exit(1);
                }
            } else {
                // 父进程
                $this->processes[$pid] = [
                    'pid' => $pid,
                    'start_time' => time(),
                    'status' => 'running'
                ];
                Log::info("启动子进程,PID: {$pid}");
            }
        } catch (\Throwable $e) {
            Log::write('forkProcess 执行错误: ' . $e->getMessage() . "\n" . $e->getTraceAsString());
            throw $e;
        }
    }

    /**
     * 清理已结束的进程
     */
    public function cleanup()
    {
        try {
            foreach ($this->processes as $pid => $info) {
                $status = 0;
                $result = pcntl_waitpid($pid, $status, WNOHANG);
                
                if ($result === -1 || $result > 0) {
                    // 进程已结束
                    unset($this->processes[$pid]);
                    if (pcntl_wifexited($status)) {
                        $exitCode = pcntl_wexitstatus($status);
                       
                        
                        // 进程结束后,检查是否还有待处理的任务
                        if (!$this->isProcessing && !empty($this->concurrentTasks)) {
                            $this->processConcurrentTasks();
                        }
                    }
                } else {
                    // 检查进程是否超时
                    if (time() - $info['start_time'] > $this->processTimeout) {
                        // 发送终止信号
                        posix_kill($pid, SIGTERM);
                        Log::warning("进程 {$pid} 执行超时,已终止");
                        unset($this->processes[$pid]);
                    }
                }
            }
        } catch (\Throwable $e) {
            Log::write('cleanup 执行错误: ' . $e->getMessage() . "\n" . $e->getTraceAsString());
            throw $e;
        }
    }

    /**
     * 获取当前运行的进程数
     * @return int
     */
    public function getProcessCount(): int
    {
        return count($this->processes);
    }

    /**
     * 获取待处理的任务数
     * @return int
     */
    public function getPendingTaskCount(): int
    {
        return count($this->concurrentTasks);
    }

    /**
     * 设置最大进程数
     * @param int $maxProcesses
     */
    public function setMaxProcesses(int $maxProcesses)
    {
        $this->maxProcesses = $maxProcesses;
    }

    /**
     * 设置最大总进程数
     * @param int $maxTotalProcesses
     */
    public function setMaxTotalProcesses(int $maxTotalProcesses)
    {
        $this->maxTotalProcesses = $maxTotalProcesses;
    }

    /**
     * 设置进程超时时间
     * @param int $timeout
     */
    public function setProcessTimeout(int $timeout)
    {
        $this->processTimeout = $timeout;
    }
}

2.调用方法

 public function testProcess()
    {
      
        
        try {
            $asyncService = new AsyncService();
            
            // 设置最大进程数
            $asyncService->setMaxProcesses(20);
            $asyncService->setMaxTotalProcesses(30);
            
            // 准备多个任务
            $tasks = [];
            for ($i = 0; $i < 20; $i++) {
                $url = "http://127.0.0.1/index/testTime?type=(".$i.")";
                $tasks[] = [
                    'app\controller\Index',
                    'curl',
                    [$url]
                ];
            }
            
            // 并发执行所有任务
            $asyncService->multiProcess($tasks);
            
            return '任务已提交';
        } catch (\Throwable $e) {
            Log::error('testProcess 执行错误: ' . $e->getMessage() . "\n" . $e->getTraceAsString());
            throw $e;
        }
        
        return "先输出文字2";
    }

3.注意需要打开 pcntl_signal pcntl_alarm pcntl_waitpid pcntl_wifexited 扩展


ThinkPHP框架实现异步执行有多种方法: - **使用ThinkPHP6实现异步任务**:首先配置队列驱动为Redis,接着创建一个继承自`think\Queue\Job`类的异步任务类,之后将任务推送到队列中,最后启动队列处理器来执行异步任务。通过这一系列操作,能在Web开发中高效处理耗时任务,提升系统性能和用户体验 [^1]。 - **利用THINKPHP5命令行的异步执行工具类**:对于消息推送、邮件发送等操作,可以使用think的命令去异步执行。定义了`AsyncCommand`类,其中`think`方法用于异步执行think命令行,`run`方法用于创建新的异步CLI进程,支持WINDOWS和LINUX系统 [^2]。 ```php namespace util; class AsyncCommand { public static function think($command,$argument = []){ $cmd = "php " . ROOT_PATH . 'think ' . $command; if($argument){ $cmd .= " " . implode(" ",$argument); } return self::run($cmd); } public static function run($cmd){ $isWin = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; if ($isWin) { $cmd = "start /b " . $cmd . ' >'.ROOT_PATH . "runtime/popen.log"; } else { $cmd = $cmd . " > /dev/null &"; } $id = popen($cmd, 'r'); pclose($id); return $cmd; } } ``` - **ThinkPHP6无需安装框架扩展实现PHP代码异步执行**:在PHP中可以用`pcntl_fork()`实现创建子进程来实现异步,但可能会产生很多僵尸进程。也可借助优秀的swoole框架等实现 [^3]。 - **ThinkPHP5执行swoole异步任务**:在控制器中创建`swoole_client`实例,连接到`swoole_server`,若连接成功则发送数据 [^4]。 ```php <?php namespace app\index\controller; use think\Controller; class Test extends Controller{ public function index(){ $client = new \swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $ret = $client->connect("23.27.127.32", 9501); if(empty($ret)){ echo 'error!connect to swoole_server failed'; } else { $client->send('blue'); } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值