PHP
pthreads

PHPのpthreads(マルチスレッディング)でソケットを捌く

まえおき

pthreadsはPHPでマルチスレッディングを扱うための拡張モジュールです。
色々と触っていたのですが、PHP7.2(pthreads 3)から大幅に刷新され、非常に使いやすい拡張モジュールとなっています。
以前まではプリミティブな値のみしかコンテキストに渡せませんでしたが、pthreads3から多くの型を渡すことが可能となりました。

渡せるようになった値の一部

  • 無名クラス
  • 無名関数

pthreads2のときと比べだいぶ敷居が下がりました。

本記事について

本記事ではPHPのpthreadsでのソケットの扱いについて触れます。

ソケットの扱いについて

以前(pthreads 2)同様pthreads3にも幾つか制約はあり、例えばAコンテキストのソケットからBコンテキストのソケットをいじると SEGVzend_mm_heap エラーが出るなど、ソケットが扱えないものかなと思っていたのですが、色々触っているうちに可能であることがわかったので、書いていきたいと思います。

ソケットを開いたり閉じたりは同コンテキスト内でしか処理できず、
したがって、同コンテキスト内で処理をなるべく完結させられるような仕組みが必要です。

とりあえず準備する

適当に PHP7.2 と pthreadsをDocker上でインストールします

Dockerfile
FROM centos:7

# Setup
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

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

# A PHP installation
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

# A pthreads installation
RUN yum -y install autoconf
RUN cd /tmp && git clone https://github.com/krakjoe/pthreads.git && cd pthreads && \
    phpize && \
    ./configure && \
    make && \
    make install

# Add an extension loading lines to php.ini
RUN echo extension=pthreads.so >> /usr/local/lib/php.ini
docker-comopse.yml
php:
  build: .
  tty: true
  ports:
    - 18080:8080
  volumes:
    - ./:/var/www/html

コンテナを作ります。

$ docker-compose up -d

ソケットを捌く

下記のようなファイルを用意します。

test.php
<?php

class test extends Thread
{
    private $socket;

    public function __construct($socket)
    {
        $this->socket = $socket;
    }

    public function run()
    {
        $client = socket_accept($this->socket);
        socket_write($client, "Hello Client\n");
        sleep(30);
        socket_close($client);
    }
}

$port = 8080;

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($socket, 0, $port);
socket_listen($socket);

$clients = [$socket];

$threads = [];

while (true) {
    $read = $write = $except = $clients;
    if (!socket_select($read, $write, $except, 0)) {
        continue;
    }
    $threads[] = $thread = new test($socket);
    $thread->start();
}

foreach ($threads as $thread) {
    $thread->join();
}

仕組みの概要としては下記のようになります。

  • 1) メインコンテキストでsocket_selectを使い接続を検知します。
  • 2) 接続を検知したら別コンテキストでsocket_acceptし、接続したクライアントに対しての処理を行います。

といったものになります。

実際に sleep(30) が入っているので、本当にスレッド化されているかテストを行います。

PHP側で下記のように起動します。

$ docker-compose exec php php /var/www/html/test.php
$ curl -XGET localhost:18080 & curl -XGET localhost:18080  & curl -XGET localhost:18080  
[1] 7149
[2] 7150
Hello Client
Hello Client
Hello Client

このように30秒待たず、 Hello Client が3つ表示され、正常にソケットのクローズもできているため、スレッドが正常に動いていることがわかりました。

ソケットを扱う注意点として

ソケットを扱う上では、同コンテキスト内で処理することが重要です。それを忘れるとSEGVします。絶対です。お姉さんとの約束だよ。

みんなも最高のpthreadsライフを贈ろう :beer: :thumbsup: :thumbsup: :thumbsup: :beer:

次は、pthreadsでPromiseの実装方法でも書こうと思います。