PHPでのマルチスレッドコンピューティング:pthread

私は最近pthreadを試しましたが、うれしい驚きでした。これは、PHPにいくつかの最も実際のスレッドで動作する機能を追加する拡張機能です。 エミュレーションも魔法も偽物もありません。すべて本物です。













私はそのような仕事を検討します。 迅速に完了する必要があるタスクのプールがあります。 PHPにはこの問題を解決する他のツールがありますが、ここでは言及していません。記事はpthreadに関するものです。







拡張機能の作成者であるJoe Watkinsが彼の記事で、マルチスレッドは必ずしも容易ではないため、これに備える必要があると警告していることは注目に値します。







恐れていない人は、さらに進んでください。







pthreadとは



Pthreadsは、PHPでマルチスレッドコンピューティングを整理する便利な方法を提供するオブジェクト指向のAPIです。 APIには、マルチスレッドアプリケーションの作成に必要なすべてのツールが含まれています。 PHPアプリケーションは、Threads、Worker、およびThreadedクラスのオブジェクトを使用して、スレッドを作成、読み取り、書き込み、実行、および同期できます。







pthreadの内部



先ほど触れた主なクラスの階層を図に示します。







スレッド化-pthreadの基礎であるため、コードを並行して実行できます。 同期のためのメソッドとその他の便利なメソッドを提供します。







スレッド Threadから継承してrun()メソッドを実装することにより、スレッドを作成できます。 run()メソッドの実行が開始され、別のスレッドでstart()メソッドが呼び出された瞬間に実行されます。 これは、スレッドが作成するコンテキストからのみ開始できます。 ストリームは、同じコンテキストでのみ結合することもできます。







労働者 永続状態。ほとんどの場合、異なるスレッドで使用されます。 オブジェクトがスコープ内にある間、またはshutdown()が強制的に呼び出されるまで使用可能です。







これらのクラスに加えて、Poolクラスもあります。 プール -ワーカーのプール(コンテナ)を使用して、スレッド化されたオブジェクトをワーカー間で分散できます。 プールは、複数のスレッドを整理する最も簡単で効率的な方法です。







理論についてはさほど悲しむことはありませんが、すぐに例を挙げてこれをすべて試してください。









複数のスレッドでさまざまな問題を解決できます。 ある特定の問題を解決することは私にとって興味深いことでした。 もう一度彼女に思い出させてください。 タスクのプールがあり、それらは迅速に完了する必要があります。







それでは始めましょう。 これを行うには、データプロバイダーMyDataProvider



(スレッド)を作成します。これは、すべてのスレッドで同じで共通です。







 /** *     */ class MyDataProvider extends Threaded { /** * @var int       */ private $total = 2000000; /** * @var int     */ private $processed = 0; /** *        * * @return mixed */ public function getNext() { if ($this->processed === $this->total) { return null; } $this->processed++; return $this->processed; } }
      
      





各スレッドには、プロバイダーへのリンクが保存されるMyWorker



(Worker)があります。







 /** * MyWorker  ,      MyWork. */ class MyWorker extends Worker { /** * @var MyDataProvider */ private $provider; /** * @param MyDataProvider $provider */ public function __construct(MyDataProvider $provider) { $this->provider = $provider; } /** *     Pool. */ public function run() { //          } /** *   * * @return MyDataProvider */ public function getProvider() { return $this->provider; } }
      
      





各プールタスクの処理(リソースをMyWork



する操作)、マルチスレッドを開始した狭いネックは、 MyWork



(スレッド)になります。







 /** * MyWork  ,     */ class MyWork extends Threaded { public function run() { do { $value = null; $provider = $this->worker->getProvider(); //    $provider->synchronized(function($provider) use (&$value) { $value = $provider->getNext(); }, $provider); if ($value === null) { continue; } //    $count = 100; for ($j = 1; $j <= $count; $j++) { sqrt($j+$value) + sin($value/$j) + cos($value); } } while ($value !== null); } }
      
      





プロバイダーからのデータはsynchronized()



取り込まれることに注意してください。 そうしないと、データの一部が複数回処理されるか、データの一部がスキップされる可能性があります。

それでは、 Pool



ですべての作業を始めましょう。







 require_once 'MyWorker.php'; require_once 'MyWork.php'; require_once 'MyDataProvider.php'; $threads = 8; //  .        //      $provider = new MyDataProvider(); //    $pool = new Pool($threads, 'MyWorker', [$provider]); $start = microtime(true); //     . //      ,     . $workers = $threads; for ($i = 0; $i < $workers; $i++) { $pool->submit(new MyWork()); } $pool->shutdown(); printf("Done for %.2f seconds" . PHP_EOL, microtime(true) - $start);
      
      





私の意見では、かなりエレガントになっています。 この例をgithubに投稿しました。







以上です! まあ、ほとんどすべて。 実際、好奇心reader盛な読者を混乱させる可能性のあるものがあります。 これはすべて、デフォルトオプションでコンパイルされた標準PHPでは機能しません。 マルチスレッドを楽しむには、PHPでZTS(Zend Thread Safety)を有効にする必要があります。







PHPのセットアップ



ドキュメントに 、PHPは--enable-maintainer-ztsオプションでコンパイルする必要があると書かれています。 自分でコンパイルしようとしたのではなく、代わりに自分用にインストールしたDebian用のパッケージを見つけました。







 sudo add-apt-repository ppa:ondrej/php-zts sudo apt update sudo apt-get install php7.0-zts php7.0-zts-dev
      
      





したがって、 php



コマンドを使用して通常の方法でコンソールから起動される同じPHPがまだあります。 したがって、Webサーバーはそれを使用します。 そして、 php7.0-zts



介してコンソールから起動できる別のPHPが登場しphp7.0-zts









その後、pthreads拡張機能をインストールできます。







 git clone https://github.com/krakjoe/pthreads.git ./configure make -j8 sudo make install echo "extension=pthreads.so" > /etc/pthreads.ini sudo cp pthreads.ini /etc/php/7.0-zts/cli/conf.d/pthreads.ini
      
      





これですべてです。 まあ...ほとんどすべて。 マルチスレッドコードを作成し、同僚のマシンのPHPが適切に構成されていないことを想像してください。 恥ずかしいですね。 しかし、方法があります。







pthreads-polyfill



ここでも、 pthreads-polyfillパッケージを提供してくれたJoe Watkinsに感謝します 。 ソリューションの本質は次のとおりです。このパッケージにはpthreads拡張機能と同じクラスが含まれており、pthreads拡張機能がインストールされていなくてもコードを実行できます。 コードだけが1つのスレッドで実行されます。

これを機能させるには、このパッケージを作曲家経由で接続するだけで、他のことは考えません。 そこで、拡張機能がインストールされているかどうかをチェックします。 拡張機能がインストールされている場合、ポリフィルはここで終了します。 それ以外の場合、コードが少なくとも1つのスレッドで機能するようにプラグインクラスが接続されます。







確認する



処理が実際に複数のスレッドで発生するかどうかを確認し、このアプローチを使用する利点を評価してみましょう。

上記の例から$threads



の値を変更し、何が起こるかを確認します。







テストが実行されたプロセッサに関する情報







 $ lscpu CPU(s): 8   : 2   : 4 Model name: Intel(R) Core(TM) i7-4700HQ CPU @ 2.40GHz
      
      





プロセッサコアのロード図を見てみましょう。 すべてが期待に沿っています。







$threads = 1









$スレド= 1







$threads = 2









$スレド= 2







$threads = 4









$スレド= 4







$threads = 8









$スレド= 8







そして今、最も重要なことは、これがすべての理由です。 リードタイムを比較します。







$スレッド ご注意 リードタイム、秒
ZTSを使用しないPHP
1 pthreadなし、ポリフィルなし 265.05
1 ポリフィル 298.26
ZTSを使用したPHP
1 pthreadなし、ポリフィルなし 37.65
1 68.58
2 26.18
3 16.87
4 12.96
5 12.57
6 12.07
7 11.78
8 11.62


最初の2行は、ポリフィルを使用すると、この例でパフォーマンスの約13%が失われたことを示しています。これは、 「すべてなし」の非常に単純なPHPの比較的線形のコードです。







次に、ZTSを使用したPHP。 ZTSを使用しないPHPと比較した場合のランタイムの大きな違い(37.65秒と265.05秒)に注意を払わないでください。PHP設定の共通点を導き出そうとしませんでした。 ZTSを使用しない場合、たとえばXDebugを有効にします。







ご覧のとおり、2つのスレッドを使用すると、プログラムの実行速度は線形コードの場合よりも約1.5倍速くなります。 4つのストリームを使用する場合-3回。







プロセッサが8コアであっても、4つ以上のスレッドが使用された場合、プログラムの実行時間はそれほど変化しなかったことに気付くでしょう。 これは、プロセッサの物理コアが4であるためと思われます。わかりやすくするために、図の形でプレートを描いています。













まとめ



PHPでは、pthreads拡張機能を使用して非常にエレガントなマルチスレッド化が可能です。 これにより、具体的なパフォーマンスが向上します。








All Articles