【PHP】parallelでマルチスレッドを試そう

# はじめに
PHPは、プログラムを上から順に実行するシングルスレッドの言語です。
今回は、**『[parallel](https://www.php.net/parallel)という拡張モジュールを使うことで、PHPでマルチスレッドを扱える！』**そうなので、試したいと思います💡

## parallelとは
- PHP拡張モジュール
- PHP 7.2以降で動作
- PHP ZTS版でのみ動作
- Webからのリクエストにも対応
- 導入が少々手間


※マルチスレッドと言えば[pthreads](https://www.php.net/manual/ja/intro.pthreads.php)が使われているそうですが、「この拡張機能はメンテナンスされておらず、終了していると考えられます。」と書かれています。PHP 7.4以降は、サポートしていないということみたい。。


# 準備
### 使用技術
- Docker

### ディレクトリ構成

```bash
.
├── Dockerfile
└── src
    └── index.php
```
``src``の中をホストと共有するような構成にしています。

### コンテナ作成

```docker:Dockerfile

FROM php:7.4-zts 

RUN apt-get update

RUN pecl install parallel \
    && docker-php-ext-enable parallel

WORKDIR /var/www/html
```

[Docker Hub](https://hub.docker.com/repository/docker/westhousek/my_php)にイメージをプッシュしてあるので、詳しくはそちらをご参照ください。

- ``zts``は、「[ZTS](https://qiita.com/hnw/items/de83634fe39574b76ba1) (Zend Thread Safety) 」のことで、要は**マルチスレッドを安全に扱うモード**です。
- ``pecl``コマンドを使って、parallelをインストールしています。[PECL](https://pecl.php.net)は、「PHPの拡張モジュールを提供してくれるサービス」です。
- ``parallel``を有効化しています。






### CLI上で実行
```
$ docker build --no-cache=true -t my_php:latest .
```
上記のDockerfileからイメージを作成します。

```
$ docker image ls
REPOSITORY    TAG       IMAGE ID       CREATED          SIZE
my_php        latest    ff3880c9f1c9   41 seconds ago   407MB
```
イメージが作成されているか確認できました。

```
docker run -itd --rm -v $PWD/src:/var/www/html/ --name my_php my_php:latest /bin/bash
```
dockerのコンテナを作成することができます。

```
$ docker exec -it my_php bash

root@0e826bad2d7d:/var/www/html# php -v
PHP 7.4.15 (cli) (built: Feb  4 2021 17:37:54) ( ZTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

root@0e826bad2d7d:/var/www/html# php -m
〜〜略〜〜
parallel
〜〜〜〜〜

root@0e826bad2d7d:/var/www/html# pecl list -a
〜〜略〜〜
Installed packages, channel pecl.php.net:
=========================================
Package  Version State
parallel 1.1.4   stable

```
コンテナの中に、コマンドを実行してみました。ちゃんと、parallelが入ってることを確認できました🥳


# マルチスレッドのコード
### サンプル1

```index.php
<?php

$runtime = new \parallel\Runtime();

$runtime->run(function(){
    echo '★';
});

for ($i = 0; $i < 100; $i++) {
    echo '.';
}
```
``$runtime->run()``で新たなプロセスが実行されます。

```
root@0e826bad2d7d:/var/www/html# php index.php
..................................................★..................................................
root@0e826bad2d7d:/var/www/html# php index.php
...........................................................★.........................................
root@0e826bad2d7d:/var/www/html# php index.php
.....................★...............................................................................
root@0e826bad2d7d:/var/www/html# php index.php
...........★.........................................................................................
root@0e826bad2d7d:/var/www/html# php index.php
....................................................................................................★
```
``★``が出力されるタイミングがバラバラです。つまり、**メインのスレッドとは別のスレッドを立ち上がり、出力していること**を意味します。


### サンプル2
``` index.php
<?php

$runtime = new \parallel\Runtime();

$runtime->run(function(){
    sleep(1);
    echo 'サブ', PHP_EOL;
});

echo 'メイン', PHP_EOL;

```

```
root@0e826bad2d7d:/var/www/html# php index.php
メイン
サブ
```
1秒経ってから、「サブ」が出力されます。このことからもメインルーチンとは別のスレッドで動いていることがわかります。


# Webからの実行
- Docker Compose

### ディレクトリ構成
```
.
├── docker-compose.yml
├── nginx
│   └── default.conf
├── php
│   └── Dockerfile
└── src
    └── index.php
```

### php 
```dockerfile:dockerfile
FROM mohsenmottaghi/php-fpm-zts:7.4

RUN pecl install parallel \
    && docker-php-ext-enable parallel

WORKDIR /var/www/html
```

先程とは、違うベースイメージを使用しています。


### nginx
```default.conf
server {
    listen 80;
    root  /var/www/html;
    index index.php index.html;
    access_log /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;
    location ~ \.php$ {
          fastcgi_split_path_info ^(.+\.php)(/.+)$;
          fastcgi_pass   app:9000;
          fastcgi_index  index.php;
          include        fastcgi_params;
          fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
          fastcgi_param  PATH_INFO $fastcgi_path_info;
      }
}
```

### コード
```php:index.php
<?php

$runtime = new \parallel\Runtime();

$runtime->run(function(){    
    echo '★';
});

for ($i = 0; $i < 10000; $i++) {
    if ($i % 200 === 0) echo "\n";
    echo '.';
}

```

``docker-compose up -d``を実行し、``localhost:8000``にアクセスすると確認できます。
![スクリーンショット 2021-02-14 0.01.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573011/99d70ab0-c08a-86b1-6766-04d025fdb138.png)


# 終わりに
もっと色々なことができるそうですが、まずはお試しでということで😌
pthreadsとは違い、Webからのリクエストでも動作するので、今後の発展に期待ですね！！！

ps. @sj-i さん、コメントいただきありがとうございました！


# 参考
- [parallel](https://www.php.net/parallel)
- [PHP7.4でもマルチスレッド！！](https://qiita.com/WhiteGrouse/items/025227aff86416a1d720)
- [拡張モジュールの導入方法](https://www.php.net/manual/ja/install.pecl.downloads.php)
- [拡張モジュールの読み込み](https://www.php.net/manual/ja/install.pecl.windows.php#install.pecl.windows.loading)