簡體   English   中英

PHP 7 (Symfony4) 中的后端多線程

[英]backend multi-threading in PHP 7 (Symfony4)

(我閱讀了其他問題,但它們指的是舊版本的 PHP 或前端多線程)

我有一個 PHP/PostgreSQL 應用程序,它有一個復雜的后端處理部分。 本質上,有一個非常大的循環(幾千次迭代)一次又一次地(有排列)遍歷相同的數據。 在每個循環中,讀取相同的數據,應用操作,將結果寫回數據庫。 循環彼此完全獨立,循環之間不保留任何結果。 事實上,為了清除對象緩存(使用 Doctrine),我每 100 次左右循環清除緩存。

所以我基本上有:

for ($i=0; $i<5000; $i++) {
   // fetch data
   // manipulate data
   // write results to a different table
}

在這些循環中從未接觸過原始數據,只填充了幾個結果表。

這目前需要幾分鍾。 在我看來,我就像一個教科書式的並行處理示例。

將其置於多個威脅中的最佳方法是什么? 我不太關心執行順序,甚至工作負載是否均勻分布(根據數據操作的性質,如果所有線程運行相同數量的循環,它們最終應該或多或少具有相同的工作負載)。 我想要的只是使用更多的 CPU 內核。

我已經在 PHP 5 中完成了多線程,它是......好吧......並不完美。 可行,但很難。 這在 PHP 7 中有改進嗎? 是否有一種相對簡單的方法可以基本上說“for (...) 並在 n 個線程中運行它”?

萬一重要,應用程序是用 Symfony4 編寫的,這個后端進程是通過控制台命令調用的。

pthreads擴展被重寫為在 v3 中使用更簡單。 它在 PHP 7.2+ 上受支持,並提供了一種在 PHP 中創建多線程應用程序的方法。

或者,由於您使用的是 Symfony - 您可以編寫簡單的控制台命令,該命令可以使用Process組件將子進程作為單獨的操作系統進程運行。 以下是來自實際項目的此類跑步者的示例:

<?php

namespace App\Command;

use App\Command\Exception\StopCommandException;
use Symfony\Component\Console\Command\LockableTrait;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;
use Webmozart\PathUtil\Path;

class ProcessingRunner extends AbstractCommand
{
    use LockableTrait;
    /**
     * @var Process[]
     */
    private $processes = [];
    /**
     * @var string[]
     */
    private $cmd;
    /**
     * @var KernelInterface
     */
    private $kernel;

    /**
     * @param KernelInterface $kernel
     */
    public function __construct(KernelInterface $kernel)
    {
        parent::__construct();
        $this->kernel = $kernel;
    }

    /**
     * {@inheritdoc}
     * @throws InvalidArgumentException
     */
    protected function configure(): void
    {
        $this
            ->setName('app:processing:runner')
            ->setDescription('Run processing into multiple threads')
            ->addOption('threads', 't', InputOption::VALUE_REQUIRED, 'Number of threads to run at once', 1)
            ->addOption('at-once', 'm', InputOption::VALUE_REQUIRED, 'Amount of items to process at once', 10);
    }

    /**
     * {@inheritdoc}
     * @throws \Symfony\Component\Process\Exception\LogicException
     * @throws InvalidArgumentException
     * @throws RuntimeException
     * @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
     * @throws \InvalidArgumentException
     * @throws \LogicException
     */
    protected function execute(InputInterface $input, OutputInterface $output): ?int
    {
        if (!$this->lock()) {
            $output->writeln('The command is already running in another process.');
            return 0;
        }
        if (extension_loaded('pcntl')) {
            $stop = function () {
                StopCommandException::throw();
            };
            pcntl_signal(SIGTERM, $stop);
            pcntl_signal(SIGINT, $stop);
            pcntl_async_signals(true);
        }
        do {
            try {
                while (\count($this->processes) < $this->getInput()->getOption('threads')) {
                    $process = $this->createProcess();
                    $process->start();
                    $this->processes[] = $process;
                }
                $this->processes = array_filter($this->processes, function (Process $p) {
                    return $p->isRunning();
                });
                usleep(1000);
            } catch (StopCommandException $e) {
                try {
                    defined('SIGKILL') || define('SIGKILL', 9);
                    array_map(function (Process $p) {
                        $p->signal(SIGKILL);
                    }, $this->processes);
                } catch (\Throwable $e) {

                }
                break;
            }
        } while (true);
        $this->release();
        return 0;
    }

    /**
     * @return Process
     * @throws RuntimeException
     * @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
     * @throws \InvalidArgumentException
     * @throws \LogicException
     * @throws InvalidArgumentException
     */
    private function createProcess(): Process
    {
        if (!$this->cmd) {
            $phpBinaryPath = (new PhpExecutableFinder())->find();
            $this->cmd = [
                $phpBinaryPath,
                '-f',
                Path::makeAbsolute('bin/console', $this->kernel->getProjectDir()),
                '--',
                'app:processing:worker',
                '-e',
                $this->kernel->getEnvironment(),
                '-m',
                $this->getInput()->getOption('at-once'),
            ];
        }
        return new Process($this->cmd);
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM