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 >= 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の処理結果を待ちます。
Promise::race
は 渡されたパラメータのいずれかの処理がresolve
もしくはreject
担った際に呼ばれます。
- 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上で、
$reject
が呼ばれた際にPromise::catch
が呼ばれます。
例)
(new \Promise\Promise(function (Resolver $resolve, Rejecter $reject) {
$reject();
}))->catch(function () {
echo 'You can see this message when `$resolve` call.';
});
- 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 は未だシリアライズに弱く、一部のクラスの実行が継承できなかったりします。 そこで子スレッド内で親スレッドの状態をほぼ再現する仕組みを提供するのがこのセーフティローダの役割です。 なお、コンテキストを再現するため、いくつかの処理を行わなければならないため、セーフティローダを使わない場合と比べ若干速度が落ちます。