PHP 开发者大会 日本 2024

pcntl_fork

(PHP 4 >= 4.1.0, PHP 5, PHP 7, PHP 8)

pcntl_fork创建当前进程的子进程

说明

pcntl_fork(): int

pcntl_fork() 函数创建一个子进程,该子进程与父进程的不同之处仅在于其 PID 和 PPID。请参阅系统的 fork(2) 手册页,以获取有关系统上 fork 如何工作的具体细节。

参数

此函数没有参数。

返回值

成功时,在父进程的执行线程中返回子进程的 PID,并在子进程的执行线程中返回 0。失败时,在父进程的上下文中返回 -1,不会创建子进程,并引发 PHP 错误。

范例

示例 #1 pcntl_fork() 示例

<?php

$pid
= pcntl_fork();
if (
$pid == -1) {
die(
'无法创建子进程');
} else if (
$pid) {
// 我们是父进程
pcntl_wait($status); // 防止僵尸进程
} else {
// 我们是子进程
}

?>

参见

添加注释

用户贡献的注释 25 个注释

up
28
sean dot kelly at mediatile dot com
14 年前
“致命错误”一直困扰着我,因为在 PHP 中无法捕获和处理这种情况。我的团队几乎所有东西都用 PHP 构建,以利用我们的核心代码库,因此找到解决此问题的方法至关重要,即脚本无法恢复地崩溃而我们却一无所知。

我们的一个后台自动化系统创建了一个“任务队列”,对于队列中的每个任务,都会包含(include())一个 PHP 模块来处理该任务。然而,有时行为不端的模块会发生致命错误并导致父脚本崩溃。

我决定尝试使用 pcntl_fork() 将任务模块与父代码隔离开来,而且似乎有效:模块内生成的致命错误会导致子任务崩溃,而等待的父进程可以简单地捕获子进程的返回码并根据需要跟踪/警告我们该问题。

当然,如果我只想 exec() 该模块并检查输出,也可以做类似的事情,但是那样我就无法获得父脚本精心准备的有状态环境的好处。这使我能够将子进程保留在父进程的运行环境的上下文中,并且不会受到致命错误停止任务队列继续处理的影响。

这是 fork_n_wait.php 供您娱乐

<?php

if (! function_exists('pcntl_fork')) die('此 PHP 安装中没有 PCNTL 函数');

for (
$x = 1; $x < 5; $x++) {
switch (
$pid = pcntl_fork()) {
case -
1:
// @失败
die('创建子进程失败');
break;

case
0:
// @子进程:在此处包含(include())行为不端的代码
print "FORK: 子进程 #{$x} 准备崩溃...\n";
generate_fatal_error(); // 未定义的函数
break;

default:
// @父进程
print "FORK: 父进程,让子进程运行...\n";
pcntl_waitpid($pid, $status);
break;
}
}

print
"完成! :^)\n\n";
?>

输出结果
php -q fork_n_wait.php
FORK: 子进程 #1 准备崩溃...
PHP 致命错误:在 ~fork_n_wait.php 的第 16 行调用未定义的函数 generate_fatal_error()
FORK: 父进程,让子进程运行...
FORK: 子进程 #2 准备崩溃...
PHP 致命错误:在 ~/fork_n_wait.php 的第 16 行调用未定义的函数 generate_fatal_error()
FORK: 父进程,让子进程运行...
FORK: 子进程 #3 准备崩溃...
PHP 致命错误:在 ~/fork_n_wait.php 的第 16 行调用未定义的函数 generate_fatal_error()
FORK: 父进程,让子进程运行...
FORK: 子进程 #4 准备崩溃...
PHP 致命错误:在 ~/fork_n_wait.php 的第 16 行调用未定义的函数 generate_fatal_error()
FORK: 父进程,让子进程运行...
完成! :^)
up
25
amatsak at chestnutsoftware dot com
18 年前
在分叉时出现 MySQL“查询期间丢失连接”问题的原因是子进程继承了父进程的数据库连接。当子进程退出时,连接被关闭。如果父进程此时正在执行查询,则它在已关闭的连接上执行,因此出现错误。

避免此问题的一个简单方法是在分叉后立即在父进程中创建一个新的数据库连接。不要忘记通过在 mysql_connect() 的第 4 个参数中传递 true 来强制建立新连接

<?php
// 创建 MySQL 连接
$db = mysql_connect($server, $username, $password);

$pid = pcntl_fork();

if (
$pid == -1 ) {
// Fork 失败
exit(1);
} else if (
$pid ) {
// 这是父进程
// 不能再使用 $db,因为它将被子进程关闭
// 相反,为我们自己创建一个新的 MySQL 连接
$db = mysql_connect($server, $username, $password, true);
} else {
// 这是子进程
// 在这里使用继承的连接做一些事情
// 它将在退出时关闭
exit(0);
?>

这样,子进程将继承旧的连接,使用它,并在退出时关闭它。父进程不会受到影响,因为它将在 fork 之后立即为自己打开一个新的连接。

希望这对您有帮助。
up
23
kenneth at fellowrock dot com
10 年前
我想为这个很棒的社区做点贡献,希望这对某些人有用。尽管 PHP 提供了线程选项和并行运行的多 curl 句柄,但我设法为非线程版本的 PHP 提供了一个解决方案,将每个函数作为其自己的进程运行。

用法: #!/usr/bin/php
用法: php -f /path/to/file

#!/usr/bin/php
<?php
function fork_process($options)
{
$shared_memory_monitor = shmop_open(ftok(__FILE__, chr(0)), "c", 0644, count($options['process']));
$shared_memory_ids = (object) array();
for (
$i = 1; $i <= count($options['process']); $i++)
{
$shared_memory_ids->$i = shmop_open(ftok(__FILE__, chr($i)), "c", 0644, $options['size']);
}
for (
$i = 1; $i <= count($options['process']); $i++)
{
$pid = pcntl_fork();
if (!
$pid)
{
if(
$i==1)
usleep(100000);
$shared_memory_data = $options['process'][$i - 1]();
shmop_write($shared_memory_ids->$i, $shared_memory_data, 0);
shmop_write($shared_memory_monitor, "1", $i-1);
exit(
$i);
}
}
while (
pcntl_waitpid(0, $status) != -1)
{
if(
shmop_read($shared_memory_monitor, 0, count($options['process'])) == str_repeat("1", count($options['process'])))
{
$result = array();
foreach(
$shared_memory_ids as $key=>$value)
{
$result[$key-1] = shmop_read($shared_memory_ids->$key, 0, $options['size']);
shmop_delete($shared_memory_ids->$key);
}
shmop_delete($shared_memory_monitor);
$options['callback']($result);
}
}
}

// 为每个函数创建大小为 1M 的共享内存块。
$options['size'] = pow(1024,2);

// 定义 2 个函数作为其自己的进程运行。
$options['process'][0] = function()
{
// 在这里添加您需要的任何内容...
// 如果您需要结果,请返回其值。
// 例如:长时间运行的进程 1
sleep(1);
return
'Hello ';
};
$options['process'][1] = function()
{
// 在这里添加您需要的任何内容...
// 如果您需要结果,请返回其值。
// 例如:
// 例如:长时间运行的进程 2
sleep(1);
return
'World!';
};
$options['callback'] = function($result)
{
// $results 是一个包含返回值的数组...
// $result[0] 对应 $options['process'][0] &
// $result[1] 对应 $options['process'][1] &
// 例如:
echo $result[0].$result[1]."\n";
};
fork_process($options);

?>

如果您想从网页获取结果,请使用 exec()。例如:echo exec('php -f /path/to/file');

继续 hacking!:)
up
27
manishpatel2280 at gmail dot com
11 年前
使用 pcntl_fork fork 进程很容易.. 但是在所有子进程完成后,我们如何控制或进一步处理呢?以下是可以实现的方法...

<?php
for ($i = 1; $i <= 5; ++$i) {
$pid = pcntl_fork();

if (!
$pid) {
sleep(1);
print
"在子进程 $i 中\n";
exit(
$i);
}
}

while (
pcntl_waitpid(0, $status) != -1) {
$status = pcntl_wexitstatus($status);
echo
"子进程 $status 完成\n";
}
?>
up
7
kexianbin at diyism dot com
12 年前
在 foreach 中 Fork

<?php
foreach ($tasks as $v)
{if ((
$pid=pcntl_fork())===-1)
{
//...
continue;
}
else if (
$pid)
{
pcntl_wait($status, WNOHANG); //防止僵尸子进程,一个 wait 对应一个子进程
}
else if (
$pid===0)
{
ob_start();//阻止输出到主进程
register_shutdown_function(create_function('$pars', 'ob_end_clean();posix_kill(getmypid(), SIGKILL);'), array());//在 exit(); 之前杀死自己,否则与父进程共享的资源将被关闭
//...
exit();//避免在子进程中进行 foreach 循环
}
}
?>
up
2
laurent dot salomon at ymail dot com
9 年前
以下示例适用于 php5.6 和 apcu。已在 Debian 7 和 Ubuntu 14.04.2 LTS 上测试通过。

这里提出了一个简单的 Job 类,它可以在具有共享内存能力的项队列上执行任务列表。

class Job
{
const
CACHE_PREFIX = 'SHM/',
WORKER_NUMBER = 10;

private
$_queues = [],
$_tasks = [],
$_pids = [],
$_workerNumber,
$_cacheHandler,
$_prefix,
$_id;

public function __construct(array $queue, $workerNumber = self::WORKER_NUMBER)
{
$count = count($queue);

$length = ceil($count / $workerNumber);

$this->_queues = array_chunk($queue, $length);

$this->setWorkerNumber($workerNumber);

$this->_prefix = self::CACHE_PREFIX . microtime(true) . '/';
}

public function setWorkerNumber($workerNumber)
{
$this->_workerNumber = $workerNumber;
}

public function __get($key)
{
return apc_fetch($this->_prefix . $key);
}

public function __set($key, $value)
{
apc_store($this->_prefix . $key, $value);
}

public function add(Closure $task)
{
$this->_tasks[] = $task->bindTo($this, $this);

return $this;
}

public function run(Closure $task = null)
{
if (isset($task))
{
$this->add($task);
}

$i = 0;

do
{
$queue = $this->_queues[$i++];

$pid = pcntl_fork();

$this->_id = $i;

if ($pid === -1)
{
die("can't fork !");
}
elseif ($pid !== 0) // main
{
$this->_pids[$pid] = $pid;
}
else // child
{
foreach($this->_tasks as $task)
{
$task($queue);
}

exit(0);
}
}
while($i < $this->_workerNumber);

do // main
{
$pid = pcntl_wait($status);

unset($this->_pids[$pid]);
}
while(count($this->_pids));
}
}

$driver = new mysqli(':host', ':user', ':pwd', ':db');

$query = 'SELECT * FROM :table LIMIT :n';

if (false !== ($res = $driver->query($query)))
{
$resultSet = [];

while($row = mysqli_fetch_assoc($res))
{
$resultSet[] = $row;
}

$job = new Job($resultSet);

$job->test = [];

$job->run(function($queue = [])
{
// 任务

foreach($queue as $value)
{
$test = $this->test;

$value['workedId'] = $this->_id;

// ...

$test[] = $value;

$this->test = $test;
}
});

print_r($job->test);
}
up
12
Tony
15 年前
如果你想在 php 页面返回给用户后执行一些代码。尝试像这样 -

<?php
function index()
{
function
shutdown() {
posix_kill(posix_getpid(), SIGHUP);
}

// 做一些初始处理

echo("Hello World");

// 切换到守护进程模式。

if ($pid = pcntl_fork())
return;
// 父进程

ob_end_clean(); // 丢弃输出缓冲区并关闭

fclose(STDIN); // 关闭所有的标准
fclose(STDOUT); // 文件描述符,因为我们
fclose(STDERR); // 是作为守护进程运行的。

register_shutdown_function('shutdown');

if (
posix_setsid() < 0)
return;

if (
$pid = pcntl_fork())
return;
// 父进程

// 现在作为守护进程运行。这个进程甚至可以在
// apachectl stop 后继续存活。

sleep(10);

$fp = fopen("/tmp/sdf123", "w");
fprintf($fp, "PID = %s\n", posix_getpid());
fclose($fp);

return;
}
?>
up
5
KrazyBox
14 年前
关于在进程 fork 时如何处理文件描述符,有很多问题。

请记住,fork() 会创建程序的副本,这意味着所有描述符都会被复制。不幸的是,对于 PHP 程序来说,这是一种相当糟糕的情况,因为大多数描述符都是由 PHP 或 PHP 扩展在内部处理的。

解决这个问题的简单且可能“正确”的方法是预先 fork,实际上不应该在程序中的许多不同点进行 fork,您只需 fork,然后委派工作。使用主/从层次结构。

例如,如果您需要有许多使用 MySQL 连接的进程,只需在建立连接之前 fork,这样每个子进程都有自己独立的 mysql 连接,并且仅由它自己管理。

通过仔细和正确的使用,fork() 可以成为一个非常强大的工具。

--请记住妥善照顾您的孩子。
up
1
qu4qu426com123 at gmail dot com
2 年前
如果您想将子进程的结果读取到基于套接字的父线程中,简单的解决方案。
希望这对某人有用。

<?php
function fork_process(...$callbacks)
{
$callbackSocket = [];
$callbacks = array_filter($callbacks, function ($callback) {
return
$callback instanceof \Closure;
});
/* 在 Windows 上需要使用 AF_INET */
$domain = (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' ? AF_INET : AF_UNIX);
$pids = [];
foreach (
$callbacks as $index => $callback) {
$setUpCallbackSocket = socket_create_pair($domain, SOCK_STREAM, 0, $callbackSocket|[$index]);
if (!
$setUpCallbackSocket) {
throw new
\Exception(
"socket_create_pair 失败。原因:" . socket_strerror(socket_last_error()),
1
);
}
$pid = pcntl_fork();
if (
$pid == 0) {
try {
$returnValue = $callback();
// 将 $sendingData 声明放在行尾,防止 socket_read 挂起
$sendingData = serialize($returnValue) . "\0\r\n";
$bufferLength = mb_strlen($sendingData, '8bit');
// 如果不声明 $bufferLength,它将被静默截断为 SO_SNDBUF 的长度
// @see https://php.net/manual/en/function.socket-write.php
// @see https://php.net/manual/en/function.socket-get-option.php
socket_write($callbackSocket|[$index][0], $sendingData, $bufferLength);
socket_close($callbackSocket|[$index][0]);
} finally {
// 显式杀死进程,否则与父进程共享的资源将被关闭
posix_kill(getmypid(), SIGTERM);
exit();
}
} else {
$pids[$index] = $pid;
}
}
$results = [];
foreach (
$pids as $index => $pid) {
pcntl_waitpid($pid, $status);
$msg = "";
while (
$resp = socket_read($callbackSocket|[$index][1], 102400)) {
$msg .= $resp;
// 防止 socket_read 挂起
if ($msg[-1] === "\n" && $msg[-2] === "\r" && $msg[-3] === "\0") {
break;
}
}
socket_close($callbackSocket|[$index][1]);
$results[] = unserialize(trim($msg));
}
return
$results;
}

$time = microtime(true);
$resp = fork_process(
function () {
sleep(1);
return
'first';
},
function () {
sleep(2);
return
'second';
},
);
echo (
microtime(true) - $time);
// 2.0448851585388
echo implode(' ', $resp);
// first second
?>
灵感来自 https://stackoverflow.com/questions/8707339/sharing-variables-between-child-processes-in-php
up
3
duerra 在 yahoo 点 com
14 年前
在某些情况下,使用 pcntl_fork() 可能会有点棘手。对于快速作业,子进程可能在父进程执行与启动进程相关的某些代码之前就已完成处理。父进程可能会在准备好处理子进程状态之前收到信号。为了处理这种情况,我在信号处理程序中将一个 id 添加到一个需要清理的进程“队列”中,以防父进程尚未准备好处理它们。

我包含了一个精简版的作业守护进程,它应该可以帮助人们走上正确的轨道。

<?php
declare(ticks=1);
// 一个非常基础的作业守护进程,你可以根据自己的需求进行扩展。
class JobDaemon{

public
$maxProcesses = 25;
protected
$jobsStarted = 0;
protected
$currentJobs = array();
protected
$signalQueue=array();
protected
$parentPID;

public function
__construct(){
echo
"已创建 \n";
$this->parentPID = getmypid();
pcntl_signal(SIGCHLD, array($this, "childSignalHandler"));
}

/**
* 运行守护进程
*/
public function run(){
echo
"正在运行 \n";
for(
$i=0; $i<10000; $i++){
$jobID = rand(0,10000000000000);

while(
count($this->currentJobs) >= $this->maxProcesses){
echo
"已达到最大子进程数,等待中...\n";
sleep(1);
}

$launched = $this->launchJob($jobID);
}

// 等待所有子进程结束后再退出
while(count($this->currentJobs)){
echo
"等待当前作业完成... \n";
sleep(1);
}
}

/**
* 从作业队列中启动一个作业
*/
protected function launchJob($jobID){
$pid = pcntl_fork();
if(
$pid == -1){
// 启动作业时出现问题
error_log("无法启动新作业,正在退出");
return
false;
}
else if (
$pid){
// 父进程
// 有时候,如果子脚本执行速度足够快,你可能会在以下代码执行之前收到子进程信号处理函数的信号!
//
$this->currentJobs[$pid] = $jobID;

// 如果在我们到达这里之前捕获到了这个 pid 的信号,它将位于我们的 signalQueue 数组中
// 所以让我们现在就处理它,就像我们刚刚收到信号一样
if(isset($this->signalQueue[$pid])){
echo
"在信号队列中找到 $pid,正在处理它 \n";
$this->childSignalHandler(SIGCHLD, $pid, $this->signalQueue[$pid]);
unset(
$this->signalQueue[$pid]);
}
}
else{
// 分叉的子进程,执行你的操作....
$exitStatus = 0; // 如果需要,可以使用错误代码或其他任何内容
echo "在 pid ".getmypid()." 中做一些有趣的事情\n";
exit(
$exitStatus);
}
return
true;
}

public function
childSignalHandler($signo, $pid=null, $status=null){

// 如果没有提供 pid,这意味着我们正在从系统获取信号。让我们找出哪个子进程结束了
if(!$pid){
$pid = pcntl_waitpid(-1, $status, WNOHANG);
}

// 确保我们获取所有已退出的子进程
while($pid > 0){
if(
$pid && isset($this->currentJobs[$pid])){
$exitCode = pcntl_wexitstatus($status);
if(
$exitCode != 0){
echo
"$pid 以状态码 .$exitCode. 退出\n";
}
unset(
$this->currentJobs[$pid]);
}
else if(
$pid){
// 糟糕,我们的作业在这个父进程甚至还没有注意到它已经启动之前就完成了!
// 让我们记录下来并在父进程准备好时处理它
echo "..... 将 $pid 添加到信号队列 ..... \n";
$this->signalQueue[$pid] = $status;
}
$pid = pcntl_waitpid(-1, $status, WNOHANG);
}
return
true;
}
}
up
2
somebody
14 年前
除了学术示例之外,你在脚本中使用 fork 时应该 **非常** 小心,
或者说,除非你非常清楚它的局限性,否则最好完全避免使用它。

问题在于,它不仅会分叉整个 PHP 进程,包括脚本的状态,
还会分叉任何已加载扩展的内部状态。
这意味着所有内存都会被复制,但所有文件描述符都在父进程和子进程之间共享。
如果某些扩展在内部维护文件描述符,这可能会导致严重破坏。
如果某些扩展在内部维护文件描述符,这可能会导致严重破坏。
主要的例子当然是 mysql,但这可能是任何维护打开文件或网络套接字的扩展。
主要的例子当然是 mysql,但这可能是任何维护打开文件或网络套接字的扩展。
主要的例子当然是 mysql,但这可能是任何维护打开文件或网络套接字的扩展。
此外,仅仅在父进程或子进程中重新打开连接并不是一个安全的方法,因为当旧的连接资源被销毁时,
扩展可能不仅仅是关闭它,例如,它可能会向服务器发送一个注销请求,使连接不可用。
扩展可能不仅仅是关闭它,例如,它可能会向服务器发送一个注销请求,使连接不可用。
扩展可能不仅仅是关闭它,例如,它可能会向服务器发送一个注销请求,使连接不可用。
例如,当 PHP 退出时,mysql 就会发生这种情况 - 在以下脚本中,查询将始终失败并显示“MySQL 服务器已断开”

<?php
mysql_connect
(/* 在这里输入一个可用的服务器? */);
if(
pcntl_fork()) die(); // 分叉一个子进程并让父进程终止
//if(pcntl_fork()) posix_kill(getmypid(),9); // 可以工作,但非常丑陋
$r=mysql_query("select 1;");
if(!
$r)die(mysql_error()."\n");
?>
(有人建议进程使用 SIGKILL 自杀以避免在关闭时进行任何清理)

(唯一安全的方法是在 fork 之后关闭所有连接并重新打开它们,即使扩展在内部保持一个连接打开,这甚至可能是不可能的)

为了很好地演示 fork 可能造成的破坏,请尝试下面的脚本。
它打开一个 mysql 连接,然后分叉,并从父进程和子进程运行查询,
验证它是否收到正确的结果。
运行它(最好在 cli 上)几次,你会发现各种可能的结果
运行它(最好在 cli 上)几次,你会发现各种可能的结果
- 经常只是挂起并且不再输出任何内容
- 也很常见,服务器关闭连接,可能是因为它收到了无法处理的交错请求。
- 有时一个进程会得到另一个进程的查询结果!(因为两者都通过同一个套接字发送查询,
- 有时一个进程会得到另一个进程的查询结果!(因为两者都通过同一个套接字发送查询,
谁收到回复纯粹是运气)
谁收到回复纯粹是运气)

<?php
mysql_connect
(/* 在这里输入一个可用的服务器地址? */);
$f=pcntl_fork();
while(
true){
sleep(rand(0,10)/100);
$r=mysql_query("select $f;");
if(!
$r)die($f.": ".mysql_error()."\n");
list(
$x)=mysql_fetch_array($r);
echo (
$f)?".":"-";
if(
$x!=$f) echo ($f.": fail: $x!=$f\n ");
}
?>
up
4
arnold 在 helderhosting 点 nl
19 年前
当 PHP 用作 Apache 模块时,不能使用 'pcntl_fork' 函数。您只能在 CGI 模式或命令行中使用 pcntl_fork。

使用此函数将导致: 'Fatal error: Call to undefined function: pcntl_fork()'(致命错误:调用未定义的函数:pcntl_fork())
up
2
simon 点 riget 在 gmail 点 com
6 年前
请注意,如果在 php.ini 中禁用了该函数,pcntl_fork 可能会返回 NULL(而不是 -1)。
<?php
$pid
= pcntl_fork();
if (
$pid < 0 || $pid === null )
die (
"无法创建子进程");
?>
up
0
jrm456 在 speed 点 1s 点 fr
9 年前
当 PHP 作为 Apache 模块运行时,pcntl_fork() 不可用的解决方法

function background_job($program, $args)
{
# 当 PHP 作为 Apache 模块运行时,以下代码不起作用
/*
$pid = pcntl_fork();
pcntl_signal(SIGCHLD, SIG_IGN);

if ($pid == 0)
{
posix_setsid();
pcntl_exec($program, $args, $_ENV);
exit(0);
}
*/

# 解决方法
$args = join(' ', array_map('escapeshellarg', $args));
exec("$program $args 2>/dev/null >&- /dev/null &");
}
up
-1
cyrus 在 hirvi 点 com
1 年前
这是 `kenneth 在 fellowrock 点 com` 提出的 fork_process 函数的 APCu 替代方案(代替共享内存) https://php.net/manual/en/function.pcntl-fork.php#115855

<?php
// 用法
$namespace = 'some_namespace';
$processes = [
function () {
sleep(1);
return
'Hello';
},
function () {
sleep(1);
return
' world!';
},
];
$callback = function ($result) {
echo
$result[0] . $result[1];
};
threadize($namespace, $processes, $callback);

function
threadize(string $namespace, $processes, callable $callback)
{
apcu_store($namespace . '_monitor', 0, 86400 * 3);
for (
$i = 0; $i < count($processes); $i++) {
$pid = pcntl_fork();
if (!
$pid) {
if (
$i == 1) {
usleep(100000);
}
apcu_store($namespace . '_process_result_' . $i, $processes[$i](), 86400 * 3);
apcu_inc($namespace . '_monitor');
exit(
0);
}
}
while (
pcntl_waitpid(0, $status) != -1) {
if (
apcu_fetch($namespace . '_monitor') === count($processes)) {
$result = [];
for (
$i = 0; $i < count($processes); $i++) {
$result[$i] = apcu_fetch($namespace . '_process_result_' . $i);
apcu_delete($namespace . '_process_result_' . $i);
}
$callback($result);
apcu_delete($namespace . '_monitor');
}
}
}
?>
up
0
John Nicholls
10 年前
关于 https://php.net/manual/en/function.posix-setsid.php 的说明描述了如何在子进程启动时通过简单调用 posix_setsid() 来避免产生大量僵尸进程。
up
-1
williamdes 在 wdes 点 fr
2 年前
<?php

declare(strict_types = 1);

/**
* 执行任务的辅助方法
*/
function execute_task(int $task_id): void
{
echo
'开始任务: ' . $task_id . PHP_EOL;

// 使用 sleep() 模拟实际工作
$execution_time = rand(5, 10);
sleep($execution_time);

echo
"完成任务: ${task_id}. 耗时 ${execution_time} 秒.\n";
}

/**
* 构建任务列表
*/
function generator(): Generator
{
$item_count = 50;
for (
$i = 1; $i <= $item_count; $i++) {
yield
$i;
}
}

/**
* 启动工作
*/
function launch(): void
{
$processCount = 0;
$status = null;
echo
'运行进程 ID: ' . getmypid() . PHP_EOL;

$taskList = generator();
do {
echo
'仍有任务待处理' . PHP_EOL;

if (
$processCount >= 5) {
echo
'等待中,当前运行进程数: ' . $processCount . PHP_EOL;
pcntl_wait($status);
$processCount--;
continue;
}

echo
'正在运行的任务数: ' . $processCount . PHP_EOL;
$task = $taskList->current();
$taskList->next();
$processCount++;

$pid = pcntl_fork();

if (
$pid === -1) {
exit(
'创建子进程失败...' . PHP_EOL);
}

if (
$pid === 0) {
$processList[] = getmypid();
echo
'以进程 ID 运行任务: ' . getmypid() . PHP_EOL;
execute_task($task);
exit();
}
} while (
$taskList->valid());

$waitForChildrenToFinish = true;
while (
$waitForChildrenToFinish) {
// 此 while 循环将暂停父进程,直到所有子进程
// 完成 - 此时脚本继续执行。
if (pcntl_waitpid(0, $status) === -1) {
echo
'并行执行完成' . PHP_EOL;
$waitForChildrenToFinish = false;
}
}

// 所有任务处理完成后要运行的代码
}

launch();
up
0
nmmm at nmmm dot nu
13 年前
即使我使用以下方式启动脚本,但在我注销几个小时后脚本被杀死,这让我很困扰:

php server.php >& logfile.txt

看起来即使我不使用标准输入,PHP 也会以某种方式与标准输入交互。

解决方法是使用 nohup 启动它:

nohup php server.php >& logfile.txt

或者将其守护化/作为守护进程运行(例如 fork() 并关闭文件描述符)
up
0
iulian
14 年前
使用 fork 在单个作业队列上运行多个子进程时,我使用 mysql_affected_rows() 来防止 worker 之间的冲突

首先,我找到一个“空闲”的作业
SELECT job_id FROM queue WHERE status="free"

然后我更新队列
UPDATE queue SET worker_id={$worker_id} WHERE job_id={$job_id}

然后我查看该行是否已更改

<?php
if(mysql_affected_rows() == 0)
{
//该行未更改,因此必须表示另一个 worker 已声明该作业,所以我返回“查找空闲作业”查询
}
else
{
//执行作业
}
?>
up
0
drrota at us dot ibm dot com
15 年前
我能够解决无法从 Apache php 运行 fork 和 exec 的问题。

我通过在 Linux 上调用系统 'at' 命令来解决这个问题。 “at run something now”。 并且您必须在 crontab 文件中设置 atrun -s(每分钟运行一次),以确保即使机器负载很重也能快速启动。

如果您是唯一在 Linux 机器上运行批处理作业的人,则此方法有效。
up
0
ben at gelbnet dot com
22 年前
我正在编写一个 shell 脚本来获取用户的输入,但是,如果用户没有输入足够的数据,我需要我的脚本在一定的秒数后超时。 下面的代码描述了我使用的方法。 这有点麻烦,但确实有效。

-Ben

#!/home/ben/php/bin/php -q
<?php
//全局变量
$RETURN_CHAR = "\n";
$TIMEOUT = 5; //输入超时的秒数
$PID = getmypid();
$CHILD_PID = 0;

//确保程序执行不会超时
set_time_limit(0);

function
set_timeout() {
global
$PID;
global
$CHILD_PID;
global
$TIMEOUT;

$CHILD_PID = pcntl_fork();
if(
$CHILD_PID == 0) {
sleep($TIMEOUT);
posix_kill($PID, SIGTERM);
exit;
}
}

function
clear_timeout() {
global
$CHILD_PID;
posix_kill($CHILD_PID, SIGTERM);
}

// read_data()
// 从 STDIN 获取一行数据并返回
function read_data() {

$in = fopen("php://stdin", "r");
set_timeout();
$in_string = fgets($in, 255);
clear_timeout();
fclose($in);
return
$in_string;
}

// write_data($outstring)
// 将数据写入 STDOUT
function write_data($outstring) {
$out = fopen("php://stdout", "w");
fwrite($out, $outstring);
fclose($out);
}

while(
1) {
write_data("说点什么->");
$input = read_data();
write_data($RETURN_CHAR.$input);
}

?>
up
-1
kentmussell at mindspring dot com
17 年前
这是我写的一个有趣的脚本。 它演示了如何将 pcntl_fork() 用作有用的工具。

<?php
/* 此脚本用于测试一个算法,该算法旨在:
a.) 比较密码哈希,或在尝试单个密码需要 10 秒钟的情况下高效地尝试密码。
b.) 生成线程以同时比较哈希。
c.) 限制一次打开的线程数。
*/
//检查可除性
function divby($num,$den) {
$result = $num/$den;
$result2 = floor($result);
if (
$result == $result2) {
return
true;
}
else {
return
false;
}
}
//检查一段时间是否符合每 10 秒发生一次的 2 秒间隔。间隔的大小可以增加或减少,以使用更多或更少的内存。
function goodTime($elapsed) {
$num = floor($elapsed);
$num = $num/12;
$min = floor($num);
$min = 12*$min;
$max = $min+2;
if (
$elapsed >= $min && $elapsed <= $max) {
return
"yes";
}
else {
return
"no";
}
}

$x = 30; //子线程数
$pid = 1; //创建第一个线程所需
$xpass = md5('29');//要破解的哈希
$time = time();
$i = 1;
//父进程生成 $x 个子进程。
while ($i <= $x) {
if (
file_exists('childcall.txt')) {
unlink('childcall.txt');
exit;
}
$elapsed = time()-$time;
//仅在每 10 秒发生的间隔期间生成子进程,为前一批子进程留出足够的时间来完成其任务。
if (goodTime($elapsed)=="yes") {
//我们是父进程吗?
if ($pid != 0) {
//诞生一个孩子。
$pid = pcntl_fork();
//记录已诞生的孩子数量。
$arr[$i] = $i;
$time2 = $elapsed;
}
//护送孩子们离开循环。
if ($pid == 0) {
$i = $x+1;
}
$i++;
}
}
//父进程等待孩子们玩耍结束。
if ($pid) {
$value = 1;
while (!
file_exists('childcall.txt')) {
//等待
}
unlink('childcall.txt');
$time = time()+2;
while (
time()<$time) {
//等待
}
exit;
}
//孩子们轮流找到最高的数组值,并将其更改为 0
rsort($arr);
$value = max($arr);
$arr[$value] = 0;
$time = time()+10;
//模拟延迟
while (time() < $time) {
//等待
}
//将高数组值哈希与我们要破解的哈希进行比较。
if (md5($value) == $xpass) {
echo
"$value \n";
}
if (
$value == $x || md5($value) == $xpass) {
$file = "childcall.txt";
$content = true;
file_put_contents($file,$contents);
}
?>
up
-2
匿名用户
15 年前
关于数据库连接,可以使用 kill 9 或 sleep 来处理,真正的问题是如果两个线程同时进行数据库查询,PHP 会开始出现随机数据库错误,这些错误不一定清楚问题是什么。

您应该为每个线程创建一个单独的链接。
up
-2
php at mx dot magic-lamp dot org
15 年前
针对 MySQL“查询期间连接丢失”或由子进程退出引起的任何其他对象相关问题的解决方法是强制子进程执行 kill -9 自杀,从而避免任何清理。当然 - 这不是太优雅,但它确实有效。

<?php
$pid
= pcntl_fork();
if (
$pid == 0 ) {
// 这是子进程。在这里做点什么。
// 我们不调用 exit(),而是使用 posix_kill()
posix_kill(getmypid(),9);
}
?>

请注意,不要生成太多进程,因为这会产生其自身的问题。
up
-5
xuecan at google dot com
18 年前
我认为这段简单的代码可以帮助理解 fork 的工作原理

<?php
echo "posix_getpid()=".posix_getpid().", posix_getppid()=".posix_getppid()."\n";

$pid = pcntl_fork();
if (
$pid == -1) die("could not fork");
if (
$pid) {
echo
"pid=".$pid.", posix_getpid()=".posix_getpid().", posix_getppid()=".posix_getppid()."\n";
} else {
echo
"pid=".$pid.", posix_getpid()=".posix_getpid().", posix_getppid()=".posix_getppid()."\n";
}
?>
To Top