可以从处理 Web 服务器请求的 PHP 脚本中分叉出一个进程,但您应该注意以下几点:
- 您的原始 PHP 进程和分叉的进程可能会共享很多数据对象。虽然一些变量可以被复制并且可以在子进程和父进程中独立操作,但一些变量(如数据库连接或文件句柄等)可能引用相同的计算资源。您应该小心避免重复使用资源变量。让孩子重新连接到您的数据库并重新打开文件等。
- 如果一个进程分叉,它可能无法连接到数据库或邮件网关等。您可能应该让这个分叉的子进程循环尝试完成它的目标,然后在一定次数的尝试后最终死掉,如果它是做一些重要的事情。
- 可能还有十几个其他关于多线程、竞争条件、资源使用等的警告。请记住,分叉进程意味着您正在消耗更多资源,这些资源可能正在被其他进程使用,您可能会得到奇怪的、难以解决的问题。
说了这么多,我有一些运气使用exec 命令的组合来分叉一个单独的进程,然后调用posix_setsid 函数,以便分叉的子进程可以完全从 apache 中分离出来,并且即使父进程完成或 apache 重新启动或其他任何情况,也继续运行。
使用此代码需要您自担风险。
从您的 CodeIgniter 脚本中,您可以使用 exec 派生一个子进程,如下所示:
// the PARENT process (a CodeIgniter script running in response to http request)
// put this code in a controller or something
// here's a command to run the PHP executable in the background on some file
// routing the output and error messages to another file
$cmd = "/usr/bin/php /path/to/child-script.php > /tmp/foo/out.txt 2>&1 & echo \$!";
// will contain an array of output lines from the exec command
$cmd_output = NULL;
// will contain the success/failure code returned by the OS.
// Will be zero for a valid command, non-zero otherwise
$cmd_result = NULL;
// $return will contain the last line from the result of
// the command which should be the PID of the process we have spawned
$cmd_return = exec ( $cmd, $cmd_output, $cmd_result );
注意:您可以改用pcntl_fork 来分叉该过程。我没有这样做的原因是因为这意味着子进程将继承 codeigniter 父脚本中的每个变量,这可能会导致一些令人困惑的行为。例如,如果一个进程更改了数据库连接,则该更改可能会突然出现在另一个脚本中,等等。此处使用 exec() 更彻底地将两个脚本分开并使事情变得更简单。如果您想更改上面的 $cmd 以运行 CodeIgniter 控制器,请参阅CodeIgniter from the Command Line。
在子进程中,您必须调用 posix_setsid() 以便子脚本将自己与父脚本分离。如果您不这样做,那么子进程可能会在父进程完成(或被杀死)或网络服务器重新启动或崩溃或其他情况时被杀死。
这是子进程中的代码,它要求在 PHP.ini 中通过 disable_functions 指令安装、启用和禁用 POSIX 扩展。此扩展在您的 Web 服务器的 PHP.ini 中经常被禁用(并且有充分的理由),但有时仍可用于 CLI 脚本。另一个使用 exec 分叉命令行进程的好理由。
// child-script.php
// CHILD process. Put your emailing code or other long-running junk in here
// because this is a brand-new, separate process, you might need to load configuration files, connect to the db, etc.
if (!extension_loaded("posix")){
throw new Exception("This script requires posix extension");
}
// NOTE: the output and results of this file will be completely unavailable to the parent script and you might never know what happens if you don't write a log file or something. Consider opening a log file somewhere
// process id of this process, should match contents of $cmd_return above
$mypid = posix_getpid();
// session id -- not to be confused with php session id
// apparently this call will make this process a 'session leader'
$sid = posix_setsid();
if ($sid < 0) {
throw new Exception("setsid failed, returned $sid");
}
// this output should be routed by the exec command above to
// /tmp/foo/out.txt
echo "setsid success, sid=$sid\n";
// PUT YOUR EMAILING CODE HERE