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
相关产品推荐
相关产品推荐

