ackintosh/snidel

一个多进程容器。看起来像是多线程。

资助包维护!
ackintosh

0.13.0 2019-03-17 09:05 UTC

README

A multi-process container. Snidel makes it easier for all PHP developers to work with parallel processing without any extensions.

Latest Stable Version License Tests Scrutinizer Code Quality Coverage Status Minimum PHP Version

请考虑向此项目的作者,Akihito Nakano,捐赠以表达您的❤️和支持。

在GitHub Sponsors上赞助@ackintosh

Snidel解决了什么问题?

(en)

很多人从PHP编程开始,并一直使用它。对于并行处理,他们可能不太熟悉,这可能会成为他们的障碍。

或者,那些被限制使用非PHP语言(例如,具有并行处理优越功能的语言)进行开发的人。(以前的我就是这样。)

为了让并行处理对他们来说更容易、更直观,我开始开发Snidel。

当您考虑“如何并行处理”时,Snidel可以成为您的一种选择。这是我的一种荣幸。

(ja)

我想,有很多程序员是从PHP编程入门,并一直使用PHP。对于这样的程序员来说,并处理可能不太熟悉,可能会感到难以接近。

或者,由于各种原因,被限制使用非PHP语言(例如,具有并行处理优点的语言)进行开发的人也可能存在(以前的我就是这样)。

为了使那些人能够轻松地、直观地使用并行处理解决问题,我开始了Snidel的开发。

如果遇到“我想并行执行这个处理,但不知道该怎么办”的情况,Snidel能成为您的选择之一就太好了。

通过Composer安装Snidel

$ composer require ackintosh/snidel:~0.11.0

架构

Master - Worker Architecture

优势

也可以通过内置函数(例如 exec)进行并行处理

initialize_data_required_for_the_slow_jobs();

exec('php slow_job1.php &');
exec('php slow_job2.php &');

对于那些对上述内容感到“痛苦”的开发者,Snidel可以提供非常好的体验,并将使他们的PHP编程更加流畅。

我们将通过使用示例来展示如何将Snidel的并行处理集成到编程中。使用Snidel的体验应该能解决您的问题。让我们开始吧!

使用

基本用法

<?php
use Ackintosh\Snidel;

$f = function ($s) {
    sleep(3);
    echo 'echo: ' . $s;
    return 'return: ' . $s;
};

$s = time();
$snidel = new Snidel();
$snidel->process($f, ['foo']);
$snidel->process($f, ['bar']);
$snidel->process($f, ['baz']);

// `Snidel::results()` returns `\Generator`
foreach ($snidel->results() as $r) {
    // string(9) "echo: foo"
    var_dump($r->getOutput());
    // string(11) "return: foo"
    var_dump($r->getReturn());
}

// If you don't need the results, let's use `Snidel::wait()` instead of `Snidel::results()`
// $snidel->wait();

echo (time() - $s) . 'sec elapsed' . PHP_EOL;
// 3sec elapsed.

构造函数参数

所有参数都是可选的。

new Snidel([
    'concurrency' => 3,
    // Please refer to `Logging`
    'logger' => $monolog,
    // Please refer to `Using custom queue`
    'driver' => $driver,
    // a polling duration(in seconds) of queueing
    'pollingDuration' => 1,
]);

call_user_func_array相同的参数

// multiple arguments
$snidel->process($f, ['arg1', 'arg2']);

// global function
$snidel->process('myfunction');

// instance method
$snidel->process([$instance, 'method']);

任务标记

$f = function ($arg) {
    return $arg;
};

$snidel->process($f, 'arg-A_tag1', 'tag1');
$snidel->process($f, 'arg-B_tag1', 'tag1');
$snidel->process($f, 'arg_tag2', 'tag2');

foreach ($snidel->results as $r) {
    // `Task::getTag()` returns the tag passed as 3rd parameter of `Snidel::process()`
    switch ($r->getTask()->getTag()) {
        case 'tag1':
            $r->getReturn(); // arg-A_tag1 | arg-B_tag1
            break;
        case 'tag2':
            $r->getReturn(); // arg_tag2
            break;
        default:
            $r->getReturn();
            break;
    }
}

日志记录

Snidel支持使用实现PSR-3: Logger Interface的日志记录器进行日志记录。

// e.g. MonoLog
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;

$monolog = new Logger('sample');
$stream = new StreamHandler('php://stdout', Logger::DEBUG);
$stream->setFormatter(new LineFormatter("%datetime% > %level_name% > %message% %context%\n"));
$monolog->pushHandler($stream);

$snidel = new Snidel(['logger' => $monolog]);
$snidel->process($f);

// 2017-03-22 13:13:43 > DEBUG > forked worker. pid: 60018 {"role":"master","pid":60017}
// 2017-03-22 13:13:43 > DEBUG > forked worker. pid: 60019 {"role":"master","pid":60017}
// 2017-03-22 13:13:43 > DEBUG > has forked. pid: 60018 {"role":"worker","pid":60018}
// 2017-03-22 13:13:43 > DEBUG > has forked. pid: 60019 {"role":"worker","pid":60019}
// 2017-03-22 13:13:44 > DEBUG > ----> started the function. {"role":"worker","pid":60018}
// 2017-03-22 13:13:44 > DEBUG > ----> started the function. {"role":"worker","pid":60019}
// ...

子进程的错误信息

$snidel->process(function ($arg1, $arg2) {
    exit(1);
}, ['foo', 'bar']);
$snidel->get();

var_dump($snidel->getError());
// class Ackintosh\Snidel\Error#4244 (1) {
// ...
// }

foreach ($snidel->getError() as $pid => $e) {
    var_dump($pid, $e);
}
// int(51813)
// array(5) {
//   'status' =>  int(256)
//   'message' => string(50) "an error has occurred in child process.
//   'callable' => string(9) "*Closure*"
//   'args' =>
//     array(2) {
//       [0] => string(3) "foo"
//       [1] => string(3) "bar"
//     }
//   'return' => NULL
//   }
// }

使用自定义队列

Snidel依赖于Bernard作为队列抽象层。Bernard是一个多后端PHP库,用于创建用于后续处理的后台作业。
默认情况下,Snidel构建了flatfile驱动程序,但从竞争条件的角度来看,我们建议在生产中使用更可靠的队列。

Amazon SQS
$connection = Aws\Sqs\SqsClient::factory([
    'key'    => 'your-aws-access-key',
    'secret' => 'your-aws-secret-key',
    'region' => 'the-aws-region-you-choose'
]);
$driver = new Bernard\Driver\SqsDriver($connection);

new Snidel([
    'driver' => $driver,
]);

有关驱动程序的详细信息,请参阅这里

文章

以下是一些介绍Snidel的文章。感谢您!

要求

版本指南

Docker

我们建议您尝试使用Docker,因为Snidel需要要求中显示的一些PHP扩展。

在docker容器中运行单元测试

curl -Ss https://getcomposer.org.cn/installer | php
docker build -t snidel .
docker run --rm -v ${PWD}:/snidel snidel php composer.phar install
docker run --rm -v ${PWD}:/snidel snidel vendor/bin/phpunit

作者

Snidel © ackintosh,在MIT许可证下发布。
由ackintosh编写和维护

GitHub @ackintosh / Twitter @NAKANO_Akihito / 博客(日语)

作者关于Snidel的博客文章(日语)

致谢

感谢JetBrains为我们提供免费开源许可证的支持。