[英]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.