jayesbe/php-process-executive

控制子进程中脚本的执行

0.9.5 2016-12-26 15:14 UTC

This package is not auto-updated.

Last update: 2024-09-23 15:00:00 UTC


README

轻松执行分叉处理。

功能

  • 执行多个并发子进程,同时保持父进程干净。
  • 作为守护进程执行,以保持父进程存活。结合 CRON 和 supervisor 实现高效且轻量级的 PHP 守护进程。

要求

  • 进程控制 (PCNTL)

安装

要安装 php-process-executive,您需要 PHP 进程控制扩展。

在 Ubuntu 上

打开命令行控制台并执行以下命令安装 PCNTL

$ pecl install pcntl

用法

以下是一个使用 ProcessExecutive 的 Symfony2 命令的示例实现

namespace FooBar\AppBundle\Command;

// use Symfony\Component\Console\Command\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ContainerAware;
use FooBar\UserBundle\Entity\User;

use ProcessExecutive\Executive;
use ProcessExecutive\ExecutiveControl;

/**
 * GenerateUsers command for testing purposes. 
 * 
 * Will generate random users
 *
 * You could also extend from Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand
 * to get access to the container via $this->getContainer().
 *
 * @author Jayesbe
 */
class GenerateUsersCommand extends ContainerAwareCommand implements ExecutiveControl
{
    const MAX_USERS = 5000000;
    
    private 
    
    $userSize,
    
    $totalGenerated,
    
    $output;
    
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->userSize = 0;
        $this->totalGenerated = 0;
        
        $this
            ->setName('foobar:generate:users')
            ->setDescription('Populate database with random users.')
            ->addArgument('size', InputArgument::OPTIONAL, 'Number of Users to generate', self::MAX_USERS)
            ->setHelp(<<<EOF
The <info>%command.name%</info> will populate the database with randomly generated users.
                    
The optional argument specifies the size of the population to generate (up to a maximum of 5 million):
        
<info>php %command.full_name%</info> 5000000
EOF
            );               
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->output = $output;

        $this->userSize = intval($input->getArgument('size'));
        if ($this->userSize > self::MAX_USERS) {
            $output->writeln("Attempted to populate with ".$this->userSize.' users. Max allowed '.self::MAX_USERS);
            return;
        }
        $output->writeln("Attempting to populate with ".$this->userSize.' users...');
        
        // we call these to make sure they are created in the parent
        $doctrine = $this->getContainer()->get('doctrine');
        $em = $doctrine->getManager();       
        
        // we need to create our Executive here
        $processor = new Executive($this);
        
        // your queue can be anything.
        $queue = array(0 => $this->userSize);
        
        // execute our queue
        $processor->execute($queue);
                
        $output->writeln(sprintf('Population Created: <comment>%s</comment> users!', $this->totalGenerated));
    }
    
    public function closeResources()
    {
        // close all db connections and memcache connections.
        // anything that requires the child to have its own resource since 
        // the parent cannot share its resources with its children. 
        $this->getContainer()->get('doctrine')->getManager()->getConnection()->close();
    }
    
    public function reloadResources()
    {
        $this->getContainer()->get('doctrine')->getManager()->getConnection()->connect();
    }
    
    public function getMaxProcesses()
    {
        // will create and maintain 8 concurrent processes
        return 8; 
    }
    
    public function getProcessItem(&$queue)
    {
        // handle your queue item.
        // since we are generating users and our queue only contains a count
        // we will generate a user id and return that as our item
        // we will then decrease the size of users we need to generate
        // the system will halt processing when the queue is empty
        
        if ($queue[0] == 0) {
            throw new \Exception('Empty Queue.');
        }
        
        $uid = uniqid("u",true);
        --$queue[0];
        $this->totalGenerated++;
        
        if ($queue[0] == 0) {
            $queue = null;
            $queue = array();
        }
        
        return $uid;
    }
    
    public function executeChildProcess($uid)
    {
        // now the main bits of processing we want done in each child.
        
        // get doctrine and entity manager and reload connection
        // this part must not use the parent process as we need to create all new object references for each child
        $doctrine = $this->getContainer()->get('doctrine');
        $em = $doctrine->getManager();
        
        // and a completely separate db connection per child
        $em->getConnection()->connect();
        
        $user = new User();
        $user->setEmail($uid.'@example.org');
        $user->setUsername($uid);
        $user->setPlainPassword($uid);
        $user->setEnabled(true);
        
        // if you use random number generate mt_rand() you need to seed for each child
        // otherwise the childs will all use the same seed from the parent. 
        // seed mt_rand
        // mt_srand();
        
        $em->persist($user);
        $em->flush();
        
        $this->output->writeln("User Generated: ".$user->getId());
    }
}

注意

Doctrine 在 CLI 环境中会不断增加内存。建议使用 PDO。但是,如果您确实想使用 Doctrine,唯一有效的方法是确保在完成操作后清除内存。我们已经在生产环境中使用了这段代码两年。我们在单次运行和守护进程模式下使用它,用于各种应用程序。我们使用它进行 Symfony 1 / Doctrine 1 和 Symfony 2 / Doctrine 2,结果相同。父进程在内存消耗和 CPU 使用方面没有变化。我们还结合使用 'nice',这使我们能够控制父进程和执行的子进程的进程优先级。

例如,上面的 Symfony2 命令可以运行为

nice -n 10 -- app/console "foobar:generate:users" -size=100

性能将取决于您的子进程执行的工作量以及它们击中磁盘的频率。在 1、2、4、8 或更多之间调整 getMaxProcess() 返回值,以查看您可以从中获得多少。

上面的 Symfony2 命令非常适合比较时间。

例如,在一个简单的 i5 上,有 4GB 的可用内存,使用

time app/console foobar:generate:users 100

2 个进程

real	0m12.589s
user	0m12.875s
sys	0m2.936s

4 个进程

real	0m10.658s
user	0m8.501s
sys	0m2.628s

8 个进程

real	0m15.807s
user	0m9.249s
sys	0m2.757s

最重要的是内存消耗。上面的 Symfony2 命令导致父进程消耗 40 MB 的内存且不会增加。该进程可以无限期地运行,而不会消耗系统中所有的内存。

法律免责声明

此软件根据 MIT 许可证发布,该许可证规定

软件按“原样”提供,不提供任何形式的保证,无论是明示的、暗示的还是与适用性、特定用途或非侵权相关的保证。在任何情况下,作者或版权所有者都不对任何索赔、损害或其他责任负责,无论是在合同、侵权或其他行为中,源于、产生于或与软件或其使用或以其他方式相关。