PHP 7.3 + Swoole で非同期処理の Coroutine を使ってもごもごする

はじめに :cat2:

以前 pthreads で非同期処理(厳密には並列処理)をする話を書きました。

と、記事を書かせていただいたのですが、実は非同期処理の拡張の有名所で、 Swoole があります。
Swooleは公式サイトにも書かれていますが、イベントドリブンな非同期処理をすることを専門に扱っている拡張機能です。

Swoole is an high-performance network framework using an event-driven, asynchronous, non-blocking I/O model which makes it scalable and efficient. It is written in C language without 3rd party libraries as PHP extension.

Swoole を使うことによって、 WebSocket, TCP/UDP通信の非同期、I/Oの監視など様々なことが行なえます。

また、 Swoole のバージョン2 では、Coroutineが導入され、通常の処理のようなものでも、非同期処理を組み込めるようになりました。

この記事は主に Coroutine について扱います

とりあえず Swoole の環境を用意する :seat:

PHP7.3のインストールと同時に、Swooleの環境を用意します。
なお PHP7.3は現時点での最新版のRC5です。

PHP7.3のインストールについては過去に書いた記事が参考になるかもしれません

Dockerfile
FROM centos:7

# Setup
RUN yum -y install epel-release wget
RUN cd /tmp && wget https://downloads.php.net/~cmb/php-7.3.0RC5.tar.gz -O php-7.3.0RC5 && tar zxvf php-7.3.0RC5

RUN yum -y upgrade

# Dependencies installation
RUN yum -y install git gcc gcc-c++ make libxml2-devel libicu-devel openssl-devel autoconf

RUN wget https://cmake.org/files/v3.12/cmake-3.12.0-Linux-x86_64.tar.gz -O cmake-3.12.0-Linux-x86_64.tar.gz && \
    tar zxvf cmake-3.12.0-Linux-x86_64.tar.gz

RUN rm -rf /usr/share/aclocal && \
    rm -rf /usr/share/applications && \
    rm -rf /usr/share/mime

RUN mv -f cmake-3.12.0-Linux-x86_64/bin/* /usr/bin/ && \
    mv -f cmake-3.12.0-Linux-x86_64/share/* /usr/share/

RUN mkdir /tmp/libzip && \
    cd /tmp/libzip && \
    curl -sSLO https://libzip.org/download/libzip-1.4.0.tar.gz && \
    tar zxf libzip-1.4.0.tar.gz && \
    cd libzip-1.4.0/ && \
    mkdir build && \
    cd build && \
    cmake ../ && \
    make && \
    make install

# PHP installation
RUN cd /tmp/php-7.3.0RC5 && \
    ./configure --enable-pcntl --enable-intl --enable-zip --enable-pdo --enable-sockets --with-openssl && \
    make && \
    make install

RUN yum -y install autoconf

RUN cd /tmp && wget https://getcomposer.org/download/1.6.5/composer.phar && \
    mv composer.phar /bin/composer && \
    chmod a=rx /bin/composer

# Swoole Installation
RUN git clone https://github.com/swoole/swoole-src.git && \
    cd swoole-src && \
    phpize && \
    ./configure && \
    make && \
    make install

# Add an extension to php.ini
RUN echo extension=swoole.so >> /usr/local/lib/php.ini

CMD cd /var/www/html && php -S 0.0.0.0:12000

docker-composeは下記のようにしています

docker-compose.yml
version: "3"
services:
  php:
    build: .
    tty: true
    volumes:
      - ./:/var/www/html
    ports:
      - 12000:12000

docker-composeで起動させます。

$ docker-compose up --build

正常にSwooleが入っているか確認する :checkered_flag:

> docker-compose exec php bash -c "php -m | grep swoole"
swoole

swoole の文字列があれば完璧 🙆

Coroutine を書いてみる :pen_fountain:

簡単な例

すごく簡単な例を書いてみます。

<?php
go(function () {
    co::sleep(3);
    echo "いぬ\n";
});

go(function () {
    co::sleep(2);
    echo "ねこ\n";
});


go(function () {
    co::sleep(1);
    echo "ちくわ\n";
});

こうしたとき、結果は

> docker-compose exec php bash -c "php /var/www/html/test.php"
ちくわ
ねこ
いぬ

となります。この時点ですごい感動 😂

co::sleep で止めつつ期待する値が出力されるかどうか試す

計算回数が違う for 文で試してみましょう

<?php
go(function () {
    echo "けーき\n";
    co::sleep(3);
    echo "ちゅ〜る\n";
});

go(function () {
    echo "さんぽ\n";
    co::sleep(1);
    echo "ねこじゃらし\n";
});

echo "日向ぼっこ\n";

期待する結果になりました。

> docker-compose exec php bash -c "php /var/www/html/test.php"
けーき
さんぽ
日向ぼっこ
ねこじゃらし
ちゅ〜る

普通の sleep を入れると

<?php
go(function () {
    echo "けーき\n";
    co::sleep(3);
    echo "ちゅ〜る\n";
});

go(function () {
    echo "さんぽ\n";
    co::sleep(1);
    echo "ねこじゃらし\n";
});

echo "日向ぼっこ\n";
sleep(2);
echo "サンマのおひたし\n";

こちらに関しては、少し期待してはいない値ではあるのですが、 sleep がそもそもすべてのコンテキストの動作を止めてしまうと考えると納得がいきそうです。

> docker-compose exec php bash -c "php /var/www/html/test.php"
けーき
さんぽ
日向ぼっこ
サンマのおひたし
ねこじゃらし
ちゅ〜る

swoole の関数を使って、非同期に通信してみる :comet:

適当に Dockerfile の CMDを変えてみます。

...
CMD cd /var/www/html && php -S 0.0.0.0:12000 index.php

index.php に下記を用意してみます。

index.php
<?php
echo '怒涛のねこ (' . $_GET['neko_power'] . ')';

test.php に下記を用意してみます。

test.php
<?php
go(function () {
    $http = new \Co\Http\Client("localhost", 12000);
    $http->get('/index.php?neko_power=nyan');
    echo $http->body . "\n";
    echo "あじのたい焼き\n";
});

go(function () {
    $http = new \Co\Http\Client("localhost", 12000);
    $http->get('/index.php?neko_power=biyo-n');
    echo $http->body . "\n";
    echo "さわらのブーメラン\n";
});

echo "ふぐのみじんぎり\n";

上記の結果は

> docker-compose exec php php /var/www/html/test.php
ふぐのみじんぎり
怒涛のねこ (nyan)
あじのたい焼き
怒涛のねこ (biyo-n)
さわらのブーメラン

になったり

> docker-compose exec php php /var/www/html/test.php
ふぐのみじんぎり
怒涛のねこ (biyo-n)
さわらのブーメラン
怒涛のねこ (nyan)
あじのたい焼き

だったりするので、正常に非同期になっていることがわかります。

Swoole についての話は以上です :whale:

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away