Skip to content

Latest commit

 

History

History
220 lines (176 loc) · 8.25 KB

README-ja.md

File metadata and controls

220 lines (176 loc) · 8.25 KB

PHP Promise

PHP PromiseはJavaScriptのPromiseのような記述で、マルチスレッディングで非同期を実現するためのライブラリです。 このライブラリはPHP拡張の pthreads を必要とし、このライブラリを利用するためには拡張をインストールする必要があります。 また、 --enable-maincontainer-zts が有効でない場合、再度PHP本体をビルドし直す必要があります。

(参照) https://github.com/krakjoe/pthreads

下記は pthreads を扱うためのDockerfileのサンプルです。

FROM centos:7

# セットアップを行います
RUN yum -y install epel-release wget
RUN cd /tmp && wget http://jp2.php.net/get/php-7.2.7.tar.gz/from/this/mirror -O php-7.2 && tar zxvf php-7.2

# 依存パッケージをインストールします
RUN yum -y install git gcc gcc-c++ make libxml2-devel libicu-devel openssl-devel

# PHPをインストールします。
RUN cd /tmp/php-7.2.7 && \
    ./configure --enable-maintainer-zts --enable-pcntl --enable-intl --enable-zip --enable-pdo --enable-sockets --with-openssl && \
    make && \
    make install

# pthreadsをインストールします
RUN yum -y install autoconf
RUN cd /tmp && git clone https://github.com/krakjoe/pthreads.git && cd pthreads && \
    phpize && \
    ./configure && \
    make && \
    make install

# pthreadsの設定をphp.iniに書き込みます
RUN echo extension=pthreads.so >> /usr/local/lib/php.ini

PHP Promiseの仕組み

PHP Promiseの仕組みは下記の通りとなります。 The Promise structure

必要な環境

  • PHP >= 7.2
  • pthreads 3

クイックスタート

composerコマンドを実行します。

$ composer require php-promise/promise:dev-master

次に下記のサンプルを実行することによりPHP Promiseを開始できます。

// 5秒後, PHP Promise は "solved It!" を出力します。
$promise = (new \Promise\Promise(function (Resolver $resolve, Rejecter $reject) {
    sleep(5);
    $resolve("Solved It!\n");
}))->then(function ($message) {
    echo $message;
});

また、 複数のPromiseの実行を待機するため、 Promise::all を使用することが可能です。

// 下記のような出力となります:
// [RESOLVE] After 3 seconds says!
// [RESOLVE] After 5 seconds says!
// Sorry, Promise was rejected.
$promises = [];
$promises[] = (new \Promise\Promise(function (Resolver $resolve, Rejecter $reject) {
    sleep(5);
    $resolve("[RESOLVE] After 5 seconds!\n");
}))->then(function ($message) {
    echo $message;
});
$promises[] = (new \Promise\Promise(function (Resolver $resolve, Rejecter $reject) {
    sleep(3);
    $reject("[REJECT] After 3 seconds!\n");
}))->then(function ($message) {
    echo $message;
});

\Promise\Promise::all($promises)->then(function () {
    echo "Okay, Promise is glad\n";
})->catch(function () {
    echo "Sorry, Promise was rejected\n";
});

// または、

\Promise\Promise::all($promises[0], $promises[1])->then(function () {
    echo "Okay, Promise is glad\n";
})->catch(function () {
    echo "Sorry, Promise was rejected\n";
});

対応しているフレームワーク

  • Laravel
    • コマンドで実行させるためにはセーフティローダを使用する必要があります。
  • CakePHP

ユースケース

  • 例えばLaravelのartisanコマンドなどで重たい処理をする際に有用です。
  • ユースケースとして、10万件以上などのレコードを処理する際にいくつかのチャンクに分けて処理するなどを並列処理化し、処理速度を高めます。

提供しているメソッド

Promise::__construct( callable $callee( Resolver $resolve, Rejecter $reject, ...$parameters ), ...$parameters ): Promise

  • $callee 関数はコンストラクタの定義時に即座に呼ばれます。.
  • $callee$resolve$reject の2つのコールバック関数のパラメータを受け取ります。
  • $resolve$callee で呼ばれると、即時に Promise::then を呼びます。.
  • $reject$callee で呼ばれると、即時に Promise::catch を呼びます。.
  • $parameters を定義すると各コールバックに値を引き渡すことが可能です。.

例)

(new \Promise\Promise(function (Resolver $resolve, Rejecter $reject) {
    // `$resolve` をPromise上で呼ぶと `then` メソッドが呼ばれます。
    $resolve();
    
    // `$reject` を呼ぶと reject メソッドが実行されます。
    $reject();
}))->then(function () {
    echo 'You can see this message when `$resolve` called.';
})->catch(function () {
    echo 'You can see this message when `$reject` called.';
});

例)

$handle = fopen('test.log', 'rw');
(new \Promise\Promise(function (Resolver $resolve, Rejecter $reject, $handle) {
    $reject($handle);
}, $handle))->then(function ($handle) {
    fwrite($handle, 'You can pass resource parameter.');
});

Promise::all( Promise ...$promises ): Promise

  • 複数のPromiseの処理結果を待ちます。

Promise::race( Promise ...$promises ): Promise

  • Promise::race は 渡されたパラメータのいずれかの処理が resolve もしくは reject 担った際に呼ばれます。

Promise::then( callable $onFulfilled, callable $rejected ): Promise

  • Promise上で、 $resolve が呼ばれた際に Promise::then が呼ばれます。

例)

(new \Promise\Promise(function (Resolver $resolve, Rejecter $reject) {
    $resolve();
}))->then(function () {
    echo 'You can see this message when `$resolve` call.';
});

Promise::catch( callable $rejected ): Promise

  • Promise上で、 $reject が呼ばれた際に Promise::catch が呼ばれます。

例)

(new \Promise\Promise(function (Resolver $resolve, Rejecter $reject) {
    $reject();
}))->catch(function () {
    echo 'You can see this message when `$resolve` call.';
});

Promise::finally( callable $onFinally ): Promise

  • Promise上で $resolve または $catch が呼ばれた際に Promise::finally が呼ばれます。

例)

(new \Promise\Promise(function (Resolver $resolve, Rejecter $reject) {
    $resolve();
}))->then(function () {
    // do something
})->catch(function () {
    // do something
})->finally(function () {
     echo 'You can see this message when `$resolve` or `$catch` called.';
});

注意事項

  • Promise の resolve 及び reject は 他スレッドから他スレッドへ値を渡す際の pthreads の仕様の都合により PHP で serialize 可能な値のみしか渡すことはできません。 例えば、 fopen で開いたストリームなどの resource などのような値は渡すことはできません。

ユニットテスト

PHPUnitでのテスト:

$ composer run-script phpunit  

コーディングスタイルのテスト:

$ composer run-script phpcs 

セーフティマネージャの役割

スレッドを扱う都合上、どうしてもスレッドを開発者自身が行う必要性がでてきます。 しかし、セーフティマネージャは開発者の負担を減らすためにPromiseに付属しています。 Promiseはセーフティマネージャ管理の元動作し、デッドロックの発生を防いだり、スレッドを安全に扱うようにします。

セーフティローダの役割

セーフティローダは、ある特定の環境下、例えば予期しないクラスのシリアライズ化などで pthreads が利用できない事象を極力回避するためのものです。 というのも pthreads は未だシリアライズに弱く、一部のクラスの実行が継承できなかったりします。 そこで子スレッド内で親スレッドの状態をほぼ再現する仕組みを提供するのがこのセーフティローダの役割です。 なお、コンテキストを再現するため、いくつかの処理を行わなければならないため、セーフティローダを使わない場合と比べ若干速度が落ちます。