You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CodeIgniter3控制器函数阻塞求助:执行时无法访问其他方法

解决CodeIgniter 3中长运行函数阻塞其他请求的问题

这是PHP单进程同步执行模型带来的典型问题——CodeIgniter 3默认是同步处理请求的,当Function1这类包含长时间循环的函数运行时,它会占用当前的服务器进程(比如PHP-FPM进程),后续请求只能排队等待这个进程释放,就导致了你看到的卡顿现象。下面给你几个实用的解决方案,按推荐程度排序:

1. 用任务队列分离请求与任务处理(最推荐)

把长耗时的任务从Web请求中剥离出来,放到队列里,让独立的Worker进程去处理,彻底避免阻塞Web请求。

实现思路:

  • 创建一个任务存储(比如数据库表、Redis),用来存放待执行的任务;
  • Function1只负责将任务信息写入存储,然后立即返回响应给用户;
  • 编写一个CLI模式的CodeIgniter脚本,作为Worker,持续从任务存储中读取未处理的任务并执行;
  • 用进程管理工具(如Supervisor、systemd)让Worker脚本一直运行,确保任务能被及时处理。

简单示例:

第一步:创建任务表

CREATE TABLE `tasks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `task_data` text NOT NULL,
  `status` enum('pending','processing','done','failed') DEFAULT 'pending',
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
);

第二步:Function1写入任务

public function function1() {
    // 准备任务数据
    $taskData = [
        'param1' => 'value1',
        'param2' => 'value2'
    ];
    
    // 插入任务到数据库
    $this->db->insert('tasks', [
        'task_data' => json_encode($taskData),
        'status' => 'pending'
    ]);
    
    // 立即返回响应
    $this->output->set_output(json_encode([
        'success' => true,
        'message' => '任务已提交,将在后台处理'
    ]));
}

第三步:编写Worker脚本(application/controllers/TaskWorker.php)

class TaskWorker extends CI_Controller {
    public function __construct() {
        parent::__construct();
        // 确保只允许CLI访问
        if (!$this->input->is_cli_request()) {
            exit('仅允许命令行访问');
        }
        $this->load->database();
    }
    
    public function run() {
        echo "Worker已启动,等待任务...\n";
        while (true) {
            // 获取一个待处理的任务
            $this->db->where('status', 'pending');
            $this->db->order_by('created_at', 'ASC');
            $task = $this->db->get('tasks', 1)->row();
            
            if ($task) {
                echo "开始处理任务ID: {$task->id}\n";
                $this->db->where('id', $task->id)->update('tasks', ['status' => 'processing']);
                
                try {
                    $taskData = json_decode($task->task_data, true);
                    // 这里执行你的循环逻辑,比如处理数据、调用其他方法
                    $this->process_task($taskData);
                    
                    $this->db->where('id', $task->id)->update('tasks', ['status' => 'done']);
                    echo "任务ID: {$task->id} 处理完成\n";
                } catch (Exception $e) {
                    $this->db->where('id', $task->id)->update('tasks', [
                        'status' => 'failed',
                        'error' => $e->getMessage()
                    ]);
                    echo "任务ID: {$task->id} 处理失败: {$e->getMessage()}\n";
                }
            }
            
            // 每隔1秒检查一次新任务,避免占用过多CPU
            sleep(1);
        }
    }
    
    private function process_task($data) {
        // 你的长循环逻辑在这里实现
        for ($i = 0; $i < 1000; $i++) {
            // 处理逻辑
        }
    }
}

第四步:启动Worker

在命令行执行:

php index.php TaskWorker run

然后用Supervisor配置让它后台持续运行,避免终端关闭后进程停止。

2. 将长任务转为异步执行

如果不想用队列,也可以直接让Function1触发一个后台进程执行任务,自己立即返回响应。

示例代码:

public function function1() {
    // 构造CLI命令,调用专门处理长任务的控制器方法
    $command = "php " . FCPATH . "index.php TaskController run_long_task > /dev/null 2>&1 &";
    // 执行命令,&表示后台运行,>/dev/null 2>&1是把输出重定向,避免阻塞
    exec($command);
    
    // 立即返回结果
    echo "任务已启动,后台执行中";
}

注意事项:

  • 确保Web服务器用户(比如www-data)有执行PHP命令的权限;
  • 如果是Windows服务器,命令写法会不同,比如用start /B php ...;
  • 建议给后台任务添加日志记录,方便排查问题。

3. 分段执行任务(适合可拆分的循环)

如果你的循环任务可以拆分成多个小阶段,可以用前端AJAX分段请求,让每个请求只处理一部分任务,避免单个请求长时间占用进程。

实现思路:

  • Function1不再一次性执行全部循环,而是每次处理一小段;
  • 用Session或数据库记录当前任务的进度;
  • 前端用JavaScript定时请求服务器,直到任务全部完成。

示例代码:

控制器方法:

public function function1_step() {
    // 从Session获取当前进度,默认0
    $progress = $this->session->userdata('task_progress') ?: 0;
    // 总任务数(根据你的实际情况调整)
    $total = 10000;
    // 每次处理100条
    $step = 100;
    
    // 处理当前分段的任务
    for ($i = $progress; $i < min($progress + $step, $total); $i++) {
        // 你的任务逻辑,比如处理一条数据
        $this->handle_single_item($i);
    }
    
    // 更新进度
    $new_progress = min($progress + $step, $total);
    $this->session->set_userdata('task_progress', $new_progress);
    
    // 返回进度信息给前端
    if ($new_progress >= $total) {
        $this->session->unset_userdata('task_progress');
        $this->output->set_output(json_encode([
            'done' => true,
            'message' => '任务完成'
        ]));
    } else {
        $this->output->set_output(json_encode([
            'done' => false,
            'progress' => round($new_progress / $total * 100, 2)
        ]));
    }
}

private function handle_single_item($index) {
    // 处理单个任务项的逻辑
}

前端JS(jQuery示例):

function runTask() {
    $.ajax({
        url: '/index.php/YourController/function1_step',
        type: 'GET',
        success: function(res) {
            res = JSON.parse(res);
            if (res.done) {
                alert(res.message);
            } else {
                console.log(`当前进度:${res.progress}%`);
                // 1秒后继续请求下一段
                setTimeout(runTask, 1000);
            }
        }
    });
}

// 启动任务
runTask();

4. 服务器配置优化(辅助缓解)

如果以上方案暂时无法实施,可以先调整服务器的PHP-FPM配置,增加可用进程数,让更多请求能同时被处理,但这只是缓解,不能从根本上解决长任务阻塞的问题。

在php-fpm.conf或对应的池配置文件中调整:

pm.max_children = 20  # 根据服务器CPU核心数和内存调整,比如1核2G服务器设为10-20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 8

调整后重启PHP-FPM服务生效。


内容的提问来源于stack exchange,提问作者Petr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:26:00