jayesbe / php-process-executive
控制子进程中脚本的执行
Requires
- php: >=5.3.3
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 许可证发布,该许可证规定
软件按“原样”提供,不提供任何形式的保证,无论是明示的、暗示的还是与适用性、特定用途或非侵权相关的保证。在任何情况下,作者或版权所有者都不对任何索赔、损害或其他责任负责,无论是在合同、侵权或其他行为中,源于、产生于或与软件或其使用或以其他方式相关。