crystalplanet/redshift

PHP 的异步编程和通信库。

dev-master 2016-10-26 11:13 UTC

This package is not auto-updated.

Last update: 2024-09-12 01:31:52 UTC


README

Build Status Code Climate

A PHP 7 库,旨在提供异步编程和通信的设施。基于 goroutines 和 core.async。

composer require crystalplanet/redshift

它解决了什么问题?

PHP 在服务网页方面非常出色。但今天我们不再谈论网页,而是谈论网络应用,这些应用比以往任何时候都更复杂,必须满足不断增长的需求。为了做到这一点,需要新的编写应用程序的方法。尽管语言已经发生了巨大变化,但在大多数情况下,它仍然被用作一个过于复杂的模板。

由于缺乏并发功能,PHP 需要像 Apache 的 mod_phpphp-fpm 这样的进程管理器才能在服务器上运行。因此,应用程序无法控制其生命周期,并且在请求之间跟踪内容变得非常复杂。

Redshift 致力于为 PHP 提供实现并行的途径,同时提供进程间通信的途径,从而为新一代“纯”应用铺平道路。

Redshift 的原理

协程

在调用普通 PHP 函数(更一般地称为 子程序)时,期望执行从第一行开始,直到遇到 return 语句或函数的末尾,或抛出异常。一旦函数返回(或抛出异常),函数的局部状态就会丢失。如果再次调用函数,则从开始处重新开始执行。

协程 是基本上在调用之间保持其状态的函数。它们不是返回值,而是 yield 一系列值。PHP 通过 生成器 提供对协程的支持。虽然一开始可能不明显,但生成器的目的就是将执行控制权返回给调用者,希望在未来能够重新获得。

Redshift 允许以最小的代码开销利用协程。事实上,redshift 代码与任何“正常”PHP 应用程序之间的唯一区别是需要在使用 yield 时进行进程间同步。

任务和事件循环

通过调用 async(),实际上是在创建一个 Task 并将其排队到事件循环中执行。尽管所有任务都在单个线程上运行,但鉴于 PHP 是单线程的,最好将任务视为非常非常便宜的进程。如果 PHP 今后允许默认使用线程,则可以将库调整为在其固定线程池上运行任务,从而实现并行执行。

每个任务都有自己的调用栈,并且与其他任务独立工作。常规函数可以异步执行,但请注意,该函数将阻塞队列中的其他所有内容,直到它完成。任务之间的同步应通过通道进行。

事件循环接受一个作为其参数的回调,该回调作为 main()。然后它开始执行 main(),以及所有其他排队的异步任务。值得注意的是,当 main 返回时,事件循环会自行终止。它不会等待可能正在等待执行的其他任务。

通道

Redshift 基于 通信顺序过程 (CSP) 的理论,该理论也是 goroutinescore.async 的基础。进程可以通过通过通道交换消息相互通信。

通道上的操作可以是异步的(puttake)和同步的(writeread)。在后一种情况下,调用总是由 yield 关键字先于,因为当前协程正在将其执行控制权返回给调用者 - 事件循环。这允许同步不同任务的执行。

通道默认使用 BlockingBuffer。这意味着如果没有任何其他协程准备从它读取,协程不允许执行超过写入此类通道的调用,反之亦然。可以使用其他缓冲区类型来实现不同的行为。

示例

您可以在 示例文件夹 中找到更多带有解释的示例。

启动 redshift 应用程序

运行 redshift 应用程序就像编写

use CrystalPlanet\Redshift\Redshift;

Redshift::run(function () {
    echo 'Hello world!' . PHP_EOL;
});
Hello World!

运行异步任务

可以使用 async() 运行异步任务,它实际上是 Redshift::async() 的别名。注意,下面的示例将只输出 Quit,因为主函数不会等待异步块的执行。

use CrystalPlanet\Redshift\Redshift;

Redshift::run(function () {

    async(function () {
        echo 'Hello World!' . PHP_EOL;
    });

    echo 'Quit' . PHP_EOL;
});
Quit

使用通道

通道可用于在主函数退出之前等待退出信号。主函数将执行,直到 $channel->read(),这将由于 $channel 为空而阻塞。一旦异步块将 Quit 写入 $channel,它将恢复。

use CrystalPlanet\Redshift\Channel\Channel;
use CrystalPlanet\Redshift\Redshift;

Redshift::run(function () {
    
    $channel = new Channel();

    async(function ($channel) {
        echo 'Hello World!' . PHP_EOL;

        yield $channel->write('Quit');
    }, $channel);

    $message = yield $channel->read();

    echo $message . PHP_EOL;
});
Hello World!
Quit

缓冲通道

可以通过将缓冲区作为第一个参数传递来创建缓冲通道。缓冲通道将允许在缓冲区满之前无阻塞地写入。

use CrystalPlanet\Redshift\Buffer\Buffer;
use CrystalPlanet\Redshift\Channel\Channel;
use CrystalPlanet\Redshift\Redshift;

Redshift::run(function () {
    
    $channel = new Channel(new Buffer(2));

    // Won't block
    yield $channel->write(true);

    // Won't block
    yield $channel->write(true);

    // Will block
    yield $channel->write(true);
});

超时

为了执行非阻塞等待,可以使用超时通道。超时通道是常规通道,在指定的时间后会发送一个值。下面的代码将输出 Hello,并在 1 秒后追加 World!

use CrystalPlanet\Redshift\Redshift;
use CrystalPlanet\Redshift\Channel\Channel;
use CrystalPlanet\Redshift\Time\Time;

Redshift::run(function () {
    $timeout = Time::after(1000);

    echo 'Hello';

    yield $timeout->read();

    echo ' World!';
});
Hello World!