thelevti/phpfork

PHP进程分叉库。

5.0.0 2020-03-28 19:20 UTC

This package is auto-updated.

Last update: 2024-09-06 17:04:04 UTC


README

要求 | 安装 | 使用

Build Status

一个简单的库,使进程分叉尽可能简单。

thelevti/phpfork遵循语义版本。更多关于semver.org

要求

  • PHP 7.2或更高版本
  • php-pcntl允许此库分叉进程。
  • php-posix允许此库获取进程信息。
  • php-shmop允许此库进行进程间通信。

安装

Composer

要在composer中使用此库,请在您的存储库根目录中运行以下终端命令。

composer require "thelevti/phpfork"

使用

此库使用命名空间TheLevti\phpfork

示例:基本进程分叉

<?php

use TheLevti\phpfork\Fork;
use TheLevti\phpfork\ProcessManager;
use TheLevti\phpfork\SharedMemory;

$manager = new ProcessManager();
$fork = $manager->fork(function (SharedMemory $shm) {
    // Do something in a forked process!
    return 'Hello from ' . posix_getpid();
})->then(function (Fork $fork) {
    // Do something in the parent process when the fork is done!
    echo "{$fork->getPid()} says '{$fork->getResult()}'\n";
});

$manager->wait();

示例:将图片上传到CDN

将迭代器输入到进程管理器中,它将工作分成多个批次,并分散到多个进程上。

<?php

use TheLevti\phpfork\ProcessManager;
use SplFileInfo;

$files = new RecursiveDirectoryIterator('/path/to/images');
$files = new RecursiveIteratorIterator($files);

$manager = new ProcessManager();
$batchJob = $manager->process($files, function(SplFileInfo $file) {
    // upload this file
});

$manager->wait();

示例:与Doctrine DBAL一起工作

当与数据库连接一起工作时,存在有关父/子进程的已知问题。有关pcntl_fork的php文档。

当分叉时,MySQL "查询期间丢失连接"问题的原因是子进程继承了父进程的数据库连接。当子进程退出时,连接关闭。如果此时父进程正在执行查询,它将在已关闭的连接上执行,因此产生错误。

这意味着在我们的示例中,我们将看到父进程中抛出的SQLSTATE[HY000]: General error: 2006 MySQL server has gone away异常。

针对这种情况的一个解决方案是使用PRE_FORK事件在分叉之前强制关闭DB连接。

<?php

use Doctrine\DBAL\DriverManager;
use TheLevti\phpfork\Batch\Strategy\ChunkStrategy;
use TheLevti\phpfork\EventDispatcher\Events;
use TheLevti\phpfork\EventDispatcher\SignalEventDispatcher;
use TheLevti\phpfork\ProcessManager;

$params = array(
    'dbname'    => '...',
    'user'      => '...',
    'password'  => '...',
    'host'      => '...',
    'driver'    => 'pdo_mysql',
);

$forks = 4;
$dataArray = range(0, 15);

$callback = function ($value) use ($params) {
    // Child process acquires its own DB connection
    $conn = DriverManager::getConnection($params);
    $conn->connect();

    $sql = 'SELECT NOW() AS now';
    $stmt = $conn->prepare($sql);
    $stmt->execute();
    $dbResult = $stmt->fetch();
    $conn->close();

    return ['pid' => getmypid(), 'value' => $value, 'result' => $dbResult];
};

// Get DB connection in parent
$parentConnection = DriverManager::getConnection($params);
$parentConnection->connect();

$dispatcher = new SignalEventDispatcher();
$dispatcher->addListener(Events::PRE_FORK, function () use ($parentConnection) {
    $parentConnection->close();
});

$manager = new ProcessManager($dispatcher, null, true);

/** @var TheLevti\phpfork\Fork $fork */
$fork = $manager->process($dataArray, $callback, new ChunkStrategy($forks));
$manager->wait();

$result = $fork->getResult();

// Safe to use now
$sql = 'SELECT NOW() AS now_parent';
$stmt = $parentConnection->prepare($sql);
$stmt->execute();
$dbResult = $stmt->fetch();
$parentConnection->close();