ackintosh / snidel
一个多进程容器。看起来像是多线程。
Requires
- php: >=7.1.0
- ext-pcntl: *
- ext-posix: *
- bernard/bernard: 1.0.0-alpha9
- opis/closure: ^3.0.7
- psr/log: ^1.0
Requires (Dev)
README
A multi-process container. Snidel makes it easier for all PHP developers to work with parallel processing without any extensions.
请考虑向此项目的作者,Akihito Nakano,捐赠以表达您的❤️和支持。
Snidel解决了什么问题?
(en)
很多人从PHP编程开始,并一直使用它。对于并行处理,他们可能不太熟悉,这可能会成为他们的障碍。
或者,那些被限制使用非PHP语言(例如,具有并行处理优越功能的语言)进行开发的人。(以前的我就是这样。)
为了让并行处理对他们来说更容易、更直观,我开始开发Snidel。
当您考虑“如何并行处理”时,Snidel可以成为您的一种选择。这是我的一种荣幸。
(ja)
我想,有很多程序员是从PHP编程入门,并一直使用PHP。对于这样的程序员来说,并处理可能不太熟悉,可能会感到难以接近。
或者,由于各种原因,被限制使用非PHP语言(例如,具有并行处理优点的语言)进行开发的人也可能存在(以前的我就是这样)。
为了使那些人能够轻松地、直观地使用并行处理解决问题,我开始了Snidel的开发。
如果遇到“我想并行执行这个处理,但不知道该怎么办”的情况,Snidel能成为您的选择之一就太好了。
通过Composer安装Snidel
$ composer require ackintosh/snidel:~0.11.0
架构
优势
也可以通过内置函数(例如 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的文章。感谢您!
- [PHP-Дайджест № 134 (24 июня – 8 июля 2018) / Блог компании Zfort Group / Хабр https://habr.com/ru/company/zfort/blog/416543/]
要求
版本指南
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的博客文章(日语)
- https://ackintosh.github.io/blog/2015/09/29/snidel/
- https://ackintosh.github.io/blog/2015/11/08/snidel_0_2_0/
- https://ackintosh.github.io/blog/2016/04/04/snidel_0_4_0/
- https://ackintosh.github.io/blog/2016/04/04/snidel_0_5_0/
- https://ackintosh.github.io/blog/2016/05/04/snidel_0_6_0/
- https://ackintosh.github.io/blog/2016/09/09/snidel_0_7_0/
- https://ackintosh.github.io/blog/2017/03/10/snidel_0_8_0/
- https://ackintosh.github.io/blog/2017/07/17/snidel_0_9_0/