スレッドとPHP







PHPとスレッド。 わずか4単語の文で、このトピックについては本を書くことができます。 いつものように、私はこれをしませんが、あなたが主題をある程度理解し始めるように情報を提供します。







一部のプログラマーが頭に抱えている混乱から始めましょう。 PHPはマルチスレッド言語ではありません。 PHP自体の内部ではPHPスレッドは使用されません。また、PHPでは、ユーザーコードがそれらを並列化メカニズムとしてネイティブに使用することを許可していません。







PHPは他のテクノロジーとはかけ離れています。 たとえば、Javaでは、スレッドは非常に頻繁に使用されますが、ユーザープログラムでも見つけることができます。 PHPではこれはそうではありません。 そして、これには理由があります。







PHPエンジンは、主に構造を単純化するために、実行のスレッドを省きます。 次のセクションを読むと、スレッドは「プログラムを高速化する魔法の技術」ではないことがわかります。 売り手のスピーチのようですね。 しかし、私たちはトレーダーではありません-私たちは技術者であり、私たちが話していることを知っています。 現在、PHPエンジンにはスレッドがありません。 おそらく将来的には登場するでしょう。 しかし、これには非常に多くの困難が伴うため、結果は予想からはほど遠いかもしれません。 主な問題は、クロスプラットフォームのマルチスレッドプログラミング(スレッドプログラミング)です。 2番目の問題は、共有リソースとロック管理です。 第三に、スレッドはすべてのプログラムに適しているわけではありません。 PHPアーキテクチャは2000年頃に誕生し、当時はストリーミングプログラミングが普及しておらず未熟ではありませんでした。 そのため、PHPの作成者(主にZendエンジン)は、スレッドなしでエンジン全体を作成することにしました。 また、安定したクロスプラットフォームマルチスレッドエンジンを作成するために必要なリソースがありませんでした。







また、スレッドはPHPユーザー空間では使用できません。 この言語はコードを正しく実行しません。 PHPの概念-「ショットアンドフォーゲット」。 次のリクエストのためにPHPを解放するために、リクエストはできるだけ早く処理する必要があります。 PHPは接続言語として作成されました。スレッドを必要とする複雑なタスクは処理しません。 代わりに、高速ですぐに使用できるリソースを使用し、すべてをまとめてユーザーに送り返します。 PHPはアクション言語であり、何かが「通常よりも長い時間」処理する必要がある場合、PHPでこれを行うべきではありません。 したがって、いくつかの重いタスクの非同期処理には、キューベースのシステムが使用されます(ギアマン、AMQP、ActiveMQなど)。 Unixでは、これを行うのが慣例です。「小型の自己完結型ツールを開発し、それらを相互に接続します。」 PHPはアクティブな並列化用に設計されたものではなく、他のテクノロジーの運命です。 すべての問題は適切なツールです。







スレッドについての一言



実行のスレッドを更新しましょう。 詳細については説明しませんが、インターネットや書籍で見つけることができます。







実行のスレッドは、プロセス内にある「小さな」作業単位(軽い作業単位処理)です。 プロセスは、実行の複数のスレッドを作成できます。 スレッドは、1つのプロセスのみの一部である必要があります。 プロセスは、オペレーティングシステム内の「大きな」処理ユニットです。 マルチコア(マルチプロセッサー)コンピューターでは、いくつかのコア(プロセッサー)が並行して動作し、実行可能タスクの負荷の一部を処理します。 プロセスAとBがキューイングの準備ができており、2つのコア(プロセッサ)が動作する準備ができている場合、AとBを同時に処理に送信する必要があります。 その後、コンピューターは単位時間(時間間隔、時間枠)ごとに複数のタスクを効率的に処理します。 これを「並列処理」と呼びます。







プロセス:







画像







実行のスレッド:







画像







すべて一緒に:







画像







AおよびBは、以前は完全に独立したハンドラーでした。 しかし、実行のスレッドはプロセスではありません。 ストリームは、プロセス内に存在するユニットです。 つまり、プロセスは、同時に実行されるいくつかの小さなタスクに作業を分散できます。 たとえば、プロセスAおよびBは、フローA1、A2、B1、およびB2を生成できます。 コンピューターに複数のプロセッサー(例えば8個)が装備されている場合、4つのスレッドすべてが同じ時間間隔(時間枠)で実行できます。







実行スレッドは、プロセスの作業を、並行して(1つの時間間隔で)解決されるいくつかの小さなサブタスクに分割する方法です。 さらに、スレッドはプロセスとほぼ同じ方法で実行されます。カーネルプログラムスレッドマネージャー(カーネルスレッドスケジューラー)は、状態を使用してスレッドを管理します。







画像







スレッドはプロセスよりも軽いので、スタックといくつかのレジスタが必要なだけです。 しかし、プロセスには多くのことが必要です。カーネルからの新しい仮想マシンフレーム(VMフレーム)、さまざまなシグナル情報、ファイル記述子、ロックなどに関する情報です。







プロセスメモリはハードウェアレベルでカーネルとMMUによって管理され、スレッドメモリはソフトウェアレベルでプログラマとスレッドライブラリによって管理されます。







そのため、スレッドはプロセスよりも軽く、管理が簡単です。 適切に使用すると、OSカーネルはスレッドの制御とディスパッチをほとんど妨げないため、プロセスよりも高速に動作します。







スレッドメモリスキーム



ランタイムスレッドには独自のスタックがあります。 したがって、関数で宣言された変数にアクセスすると、このデータの独自のコピーを取得します。







プロセスヒープは、グローバル変数やファイル記述子と同様にスレッドによって共有されます。 これは長所でもあり短所でもあります。 グローバルメモリからの読み取りのみを行う場合は、時間どおりにこれを行う必要があります。 たとえば、ストリームXの後、ストリームYの前です。グローバルメモリに書き込む場合、そこに複数のストリームを記録しようとする試みがないことを確認する価値があります。 そうしないと、このメモリ領域は予測不可能な状態、いわゆる競合状態になります。 これがストリーミングプログラミングの主な問題です。







同時アクセスの場合、再入可能性や同期ルーチンなどのいくつかのメカニズムをコードに実装する必要があります。 再入力は並行性に違反します。 また、同期により、一貫性を予測可能な方法で管理できます。







プロセスはメモリを共有せず、OSレベルで理想的に分離されます。 また、1つのプロセス内の実行スレッドは大量のメモリを共有します。







したがって、セマフォやミューテックスなど、共有メモリへのアクセスを同期するツールが必要です。 これらのツールの動作は「ブロック」の原則に基づいています。リソースがロックされ、スレッドがそれにアクセスしようとすると、デフォルトでスレッドはリソースのロック解除を待機します。 したがって、実行のスレッドだけではプログラムが高速化されることはありません。 スレッド間でタスクを効率的に分散し、共有メモリロックを管理しないと、実行スレッドのない単一のプロセスを使用するよりもプログラムの実行がさらに遅くなります。 スレッドがお互いを常に待ち続けるというだけです(そして、デッドロックや飢vなどについて話しているわけではありません)。







ストリーミングプログラミングの経験がない場合は、難しい作業であることがわかります。 ワークフローの経験を積むには、何時間もの練習とWTFの瞬間の解決が必要です。 ちょっとしたことは忘れておく価値があります。プログラム全体が破壊されます。 1つのプロセスに数百または数千のスレッドを含む実際のプロジェクトについて話している場合、スレッドを使用するよりもスレッドを使用してプログラムをデバッグする方が困難です。 あなたは夢中になり、これらすべてにjustれます。







ストリームプログラミングは難しい作業です。 マスターになるには、多くの時間と労力を費やす必要があります。







このスレッド共有スキームは常に便利であるとは限りません。 そのため、スレッドローカルストレージ(スレッドローカルストレージ、TLS)が登場しました。 TLSは、「1つのフローに属し、他のフローには使用されないグローバル」と説明できます。 これらは、特定の実行スレッド専用のグローバル状態を反映するメモリ領域です(プロセスのみを使用する場合のように)。 スレッドを作成すると、プロセスヒープの一部(ストレージ)が割り当てられます。 ストリームライブラリには、このリポジトリに関連付けられているキーが要求されます。 リポジトリにアクセスするたびに実行スレッドで使用する必要があります。 ストリームの寿命の終わりに割り当てられたリソースを破棄するには、デストラクタが必要です。







グローバルリソースへのすべての呼び出しが完全に制御され、完全に予測可能な場合、スレッドセーフアプリケーション。 そうしないと、スケジューラーが道を切り開きます。一部のタスクが予期せず実行され、パフォーマンスが低下します。







ストリームライブラリ



スレッドには、OSカーネルの支援が必要です。 オペレーティングシステムでは、1990年代半ばにスレッドが登場したため、スレッドを操作するための手法は洗練されました。







しかし、クロスプラットフォームの問題があります。 WindowsシステムとUnixシステムには特に多くの違いがあります。 これらのエコシステムは、さまざまなストリーミング実行モデルを採用しており、さまざまなストリーミングライブラリを使用しています。







Linuxでは、カーネルはclone()を呼び出してスレッドまたはプロセスを作成します。 しかし、非常に複雑であるため、毎日のストリーミングプログラミングを容易にするために、システムコールはCコードを使用します。 Libcは引き続きストリーミング操作を制御しません(C11の標準ライブラリは同様のイニシアチブを示します)。外部ライブラリはこれを行います。 今日、Unixシステムでは、通常pthreadが使用されます(他のライブラリもあります)。 PthreadはPosixスレッドの略です。 このスレッドの使用とその動作のPOSIX正規化は、1995年に遡ります。 スレッドが必要な場合は libpthreadライブラリを有効にします。- lpthreadをGCCに渡します。 Cで書かれており、 コードはオープンで 、独自のバージョン管理とバージョン管理メカニズムがあります。







そのため、Unixシステムでは、 pthreadライブラリが最も頻繁に使用されます。 並行性を提供し、並行性は特定のOSとコンピューターに依存します。







一貫性とは、複数のスレッドが同じプロセッサでランダムに実行される場合です。 並行性とは、複数のスレッドが異なるプロセッサで同時に実行されている場合です。







一貫性:







画像







並行性:







画像







PHPとスレッド



開始するには、覚えておいてください:









では、PHPの実行スレッドはどうでしょうか?







PHPがリクエストを処理する方法



問題は、PHPがHTTPリクエストをどのように処理するかです。 Webサーバーは、複数のクライアントに同時にサービスを提供するために、ある種の一貫性(または並行性)を提供する必要があります。 実際、あるクライアントに答えると、他のすべてのクライアントを一時停止させることは不可能です。







したがって、サーバーは通常、複数のプロセスまたは複数のスレッドを使用してクライアントに応答します。







歴史的に、プロセスはUnixで動作します。 Unixの基礎にすぎません。その誕生には、新しいプロセスを作成( fork()



)、それらをwaitpid()



exit()



)、同期( wait()



waitpid()



)できるプロセスもあります。 このような環境では、多数のPHPが多数のクライアント要求を処理します。 しかし、 誰もが自分のプロセスで働いています







この状況では、PHPは何の役にも立ちません。プロセスは完全に分離されています。 クライアントAのデータに関するリクエストAを処理するプロセスAは、クライアントBのリクエストBを処理するプロセスBと対話(読み取りまたは書き込み)できません。これが必要です。







98%のケースでは、 php- fpmmpm_prefork備えたApacheの2つのアーキテクチャが使用されています







Windowsでは、スレッドを使用するUnixサーバーのように、すべてがより複雑です。







Windowsは本当に素晴らしいOSです。 唯一の欠点があります-クローズドソース。 しかし、オンラインまたは書籍では、多くの技術リソースの内部構造に関する情報を見つけることができます。 Microsoftのエンジニアは Windowsの仕組みについて多くのことを語っています。







Windowsには、一貫性と並行性に対する異なるアプローチがあります。 このOSは、スレッドスレッドを非常に積極的に使用します。 基本的に、Windowsでのプロセスの作成は非常に難しいタスクであるため、通常は回避されます。 代わりに、常にどこでもスレッドが適用されます。 Windowsのストリームは、Linuxよりも桁違いに強力です。 はい、正確に。







PHPがWindows上で実行される場合、Webサーバー(any) は、クライアント要求をprocessesではなくスレッドで処理します 。 つまり、このような環境では、PHPはスレッドで実行されます。 そのため、スレッド仕様に特に注意する必要がありますスレッドセーフである必要があります







PHPは、スレッドセーフである必要があります。つまり、PHPが作成したものではなく、その中で、そしてそれで動作する一貫性を管理しなければなりません。 つまり、独自のグローバル変数へのアクセスを保護します。 また、PHPには多くの機能があります。







Zend Thread Safetyレベル(ZTS、 Zend Thread Safety )は、この保護を担当します。







スレッドを使用してクライアント要求処理を並列化する場合、Unixでも同じことが当てはまることに注意してください。 しかし、Unixシステムの場合、古典的なプロセスがこのようなタスクに伝統的に使用されているため、これは非常に珍しい状況です。 スレッドの選択に悩む人はいませんが、パフォーマンスを改善できます。 スレッドはプロセスよりも軽いため、システムはより多くのスレッドを実行できます。 また、PHP拡張機能にスレッドセーフ(ext / pthreadなど)が必要な場合は、スレッドセーフなPHPが必要になります。







ZTS実装の詳細



ZTSは、 --enable-maintainer-ztsを使用してアクティブ化されます。 通常、WindowsでPHPを実行しない場合、またはエンジンをスレッドセーフにする必要がある拡張機能を使用してPHPを実行しない場合、このスイッチは必要ありません。







現在の動作モードを確認するには、いくつかの方法があります。 CLIとphp –v



は、NTS(スレッドセーフではない)またはZTS(スレッドセーフ)がアクティブになったことを通知します。







画像







phpinfo()



使用することもできます:







画像







コード内のPHPからPHP_ZTS



定数を読み取ることができます。







 if (PHP_ZTS) { echo "You are running a thread safe version of the PHP engine"; }
      
      





ZTSでコンパイルすると、PHPの基盤全体がスレッドセーフになります。 ただし、アクティブ化された拡張機能はスレッドセーフではない場合があります。 公式の拡張機能(PHPで配布)はすべて安全ですが、サードパーティの拡張機能を保証することはできません。 PHP拡張機能のスレッドセーフをマスターするには、APIを特別に使用する必要があることがわかります。 そして、これはスレッドで絶えず発生するため、1つ省略されます- サーバー全体が崩壊する可能性があります







実行スレッドを使用する場合、リエントラント関数(通常libcから)を呼び出さないか、真のグローバル変数に盲目的にアクセスしないと、すべての兄弟スレッドで奇妙な動作が発生します 。 たとえば、1つの拡張機能でスレッドが混乱する-これは、サーバーのすべてのスレッドで提供されるすべてのクライアントに影響します! ひどい状況:1人のクライアントが他のすべてのクライアントデータを台無しにすることができます。







PHP拡張機能を設計する場合:









リエントラント関数



PHP拡張モジュールを設計するときは、 リエントラント関数を使用します 。その関数の操作はグローバル状態に依存しません。 これはあまりにも単純すぎますが。 より詳細には、前の呼び出しが完了するまで、再入可能な関数を呼び出すことができます。 2つ以上のスレッドで並行して動作できます。 グローバルステートを使用した場合、リエントラントにはなりません。 ただし、独自のグローバル状態をブロックできるため、スレッドセーフになります。)libcの従来の関数の多くは、スレッドがまだ発明されていないときに作成されたため、信頼できません。







そのため、一部のlibc(特にglibc)は、接尾辞_r()



持つ関数として再入可能な同等の関数を公開しています。 新しいC11標準には、スレッドを使用するためのより多くのオプションが用意されています。 また、C11 libcの関数が再設計され、接尾辞_s()



たとえば、 localtime_s()



)。







strtok() => strtok_r(); strerror(), strerror_r(); readdir() => readdir_r()



strtok() => strtok_r(); strerror(), strerror_r(); readdir() => readdir_r()



-など







PHP自体は、主にクロスプラットフォームで使用するためのいくつかの機能を提供します。 main / reentrancy.cを見てください







また、独自のC関数を作成するときは、再入可能性を忘れないでください。 関数は、必要なすべてを引数の形式で(スタック上またはレジスタを介して)渡すことができ、グローバル/静的変数または再入不可能な関数を使用しない場合、再入可能になります。







スレッドセーフライブラリに接続しないでください



ストリーミングプログラミングでは、メモリイメージを共有するプロセス全体が重要であることを忘れないでください。 これには、リンクされたライブラリが含まれます。







拡張機能が確実にスレッドセーフなライブラリに関連付けられている場合、ライブラリをグローバル状態にアクセスしないように保護するために、スレッドの安全性を確保する独自の方法を開発する必要があります。 ストリーミングプログラミングとCでは、これは頻繁に発生しますが、見落とされがちです。







ZTSを使用する



ZTSは、PHP 7でTLS(スレッドローカルストレージ)を使用してグローバルストリーミング変数へのアクセスを制御するコードレベルです。







PHP言語とその拡張機能を開発するとき、コード内の2つのタイプのグローバルを区別する必要があります。







単純な従来のグローバルC変数である真のグローバルがあります。これらはアーキテクチャには問題ありませんが、スレッドの一貫性から保護しなかったため、PHPがリクエストを処理するときにのみ読み取ることができます。 少なくとも1つの実行スレッドが作成される前に 、真のグローバルが作成および書き込まれます。 PHPの内部用語では、このステップはモジュールinitと呼ばれます 。 これは、拡張機能の例で明確に見られます。







 static int val; /*   */ PHP_MINIT(wow_ext) /*   PHP */ { if (something()) { val = 3; /*     */ } }
      
      





この擬似コードは、PHP拡張機能がどのように見えるかを示しています。 拡張機能には、PHPライフサイクル中に初期化されるいくつかのフックがあります。 MINIT()インターセプターは、PHPの初期化を指します。 この手順を使用すると、PHPが起動し、上記の例のように、グローバル変数を安全に読み取りまたは書き込みできます。







2番目の重要なインターセプターはRINIT()、つまりリクエストの初期化です。 このプロシージャは、新しいリクエストが処理されるたびに各拡張機能に対して呼び出されます。 つまり、RINIT()は何千回も拡張と呼ばれます。 この時点で、 PHPはすでに流れています。 Webサーバーは元のプロセスをスレッドに分割するため 、RINIT()でスレッドセーフが必要です 。 これは、複数の要求を同時に処理するスレッドが作成される状況では完全に論理的です。 スレッドを作成していないことを忘れないでください 。 PHPの代わりに、ストリームはWebサーバーによって作成されます。







スレッドグローバルも使用します 。 これらは、ZTSレベルによってスレッドの安全性が確保されているグローバル変数です。







 PHP_RINIT(wow_ext) /*    PHP */ { if (something()) { WOW_G(val) = 3; /*     */ } }
      
      





ストリーミンググローバルにアクセスするために、 WOW_G()



マクロを使用しWOW_G()



。 仕組みを見てみましょう。







マクロの必要性



要確認:PHPがスレッドで実行される場合、リクエストに関連するすべてのグローバル状態へのアクセスを保護する必要があります。 スレッドがない場合、この保護は必要ありません。 結局のところ、各プロセスは独自のメモリを受け取り、他の誰もそれを使用しません。







そのため、クエリに関連するグローバルにアクセスする方法は環境によって異なります(マルチタスクエンジンが使用されます)。 したがって、環境に関係なく、クエリに関連付けられたグローバルへのアクセスが等しく実行されることを確認する必要があります。







これにはマクロが使用されます。







WOW_G()



マクロは、マルチタスクPHPエンジン(プロセスまたはスレッド)の作業に応じて、さまざまな方法で処理されます。 拡張機能を再コンパイルすることで、これに影響を与えることができます。 したがって、ZTSモードと非ZTSモードを切り替える場合、PHP拡張は互換性がありません。 バイナリ非互換!







ZTSはnonZTSとバイナリ互換ではありません。 あるモードから別のモードに切り替える場合、例外を再コンパイルする必要があります。







プロセスで作業する場合、 WOW_G()



マクロは通常次のように処理されます。







 #ifdef ZTS #define WOW_G(v) wow_globals.v #endif
      
      





ストリームで作業する場合:







 #ifndef ZTS #define WOW_G(v) wow_globals.v #else #define WOW_G(v) (((wow_globals *) (*((void ***) tsrm_get_ls_cache()))[((wow_globals_id)-1)])->v) #endif
      
      





ZTSモードではより難しくなります。







プロセスで作業する場合-非ZTS(非Zendスレッドセーフ)モード-真のグローバルwow_globals使用されます。 この変数は、グローバル変数を含む構造体であり、マクロを使用して各変数を参照します。 WOW_G(foo)



wow_globals.foo



ます。 当然、起動時にリセットされるように、この変数を宣言する必要があります。 これもマクロを使用して実行されます(ZTSモードでは異なる方法で実行されます)。







 ZEND_BEGIN_MODULE_GLOBALS(wow) int foo; ZEND_END_MODULE_GLOBALS(wow) ZEND_DECLARE_MODULE_GLOBALS(wow)
      
      





次に、マクロは次のように処理されます。







 #define ZEND_BEGIN_MODULE_GLOBALS(module_name) typedef struct _zend_##module_name##_globals { #define ZEND_END_MODULE_GLOBALS(module_name) } zend_##module_name##_globals; #define ZEND_DECLARE_MODULE_GLOBALS(module_name) zend_##module_name##_globals module_name##_globals;
      
      





それだけです。 プロセスで作業する場合-複雑なことは何もありません。







しかし、ZTSを使用してスレッドで作業する場合、真のCグローバルはありませんが、グローバル宣言は同じように見えます。







 #define ZEND_BEGIN_MODULE_GLOBALS(module_name) typedef struct _zend_##module_name##_globals { #define ZEND_END_MODULE_GLOBALS(module_name) } zend_##module_name##_globals; #define WOW_G(v) (((wow_globals *) (*((void ***) tsrm_get_ls_cache()))[((wow_globals_id)-1)])->v)
      
      





ZTSおよびnonZTSでは、グローバルは同じ方法で宣言されます。







しかし、それらへのアクセスは異なります。 ZTSでは、 tsrm_get_ls_cache()



関数がtsrm_get_ls_cache()



ます。 これは、現在の特定の実行スレッドに割り当てられたメモリ領域を返すTLSリポジトリの呼び出しです。 そもそもvoid型にキャストしていることを考えると、このコードはそれほど単純ではありません。







TSRMレベル



ZTSは、いわゆるTSRMレベル-スレッドセーフリソースマネージャーを使用します。 これは単なるCコードの一部であり、それ以上のものではありません!







ZTSが機能できるのはTSRMレベルのおかげです。 ほとんどの場合、 PHPソースコードの/ TSRMフォルダーにあります。







TSRMは理想的なレベルではありません。 一般に、それは適切に設計され、PHP 5の時代(2004年頃)に登場しました。 TSRMは、Gnu Portable Thread、Posix Threads、State Threads、Win32 Threads、BeThreadsなど、いくつかの低レベルのストリームライブラリで動作します。 構成中に目的のレベルを選択できます(./configure --with-tsrm-xxxxx)。







TSRMの分析では、pthreadベースの実装についてのみ説明します。







ダウンロードTSRM



モジュールの初期化中にPHPがロードされると、すぐにtsrm_startup()



tsrm_startup()



ます。 PHPは、作成するスレッドの数と、スレッドの安全性を確保するために必要なリソースの数をまだ認識していません。 それぞれ1つの要素で構成されるスレッドテーブルを準備します。 テーブルは後で大きくなりますが、現時点ではmalloc()



を使用して配布されます。







この初期段階も重要です。ここでは、TLSキーとTLSミューテックスを作成します。これらは同期する必要があります。







 static pthread_key_t tls_key; TSRM_API int tsrm_startup(int expected_threads, int expected_resources, int debug_level, char *debug_filename) { pthread_key_create( &tls_key, 0 ); /* Create the key */ tsrm_error_file = stderr; tsrm_error_set(debug_level, debug_filename); tsrm_tls_table_size = expected_threads; tsrm_tls_table = (tsrm_tls_entry **) calloc(tsrm_tls_table_size, sizeof(tsrm_tls_entry *)); if (!tsrm_tls_table) { TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate TLS table")); return 0; } id_count=0; resource_types_table_size = expected_resources; resource_types_table = (tsrm_resource_type *) calloc(resource_types_table_size, sizeof(tsrm_resource_type)); if (!resource_types_table) { TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate resource types table")); free(tsrm_tls_table); tsrm_tls_table = NULL; return 0; } tsmm_mutex = tsrm_mutex_alloc(); /*   */ } #define MUTEX_T pthread_mutex_t * TSRM_API MUTEX_T tsrm_mutex_alloc(void) { MUTEX_T mutexp; mutexp = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t)); pthread_mutex_init(mutexp,NULL); return mutexp; }
      
      





TSRMリソース



TSRMレイヤーが読み込まれたら、新しいリソースを追加する必要があります。 リソースは、通常PHP拡張に関連するグローバル変数のセットを含むメモリ領域です。 リソースは現在の実行スレッドに属しているか、アクセスに対して保護されている必要があります。







このメモリ領域にはある程度のサイズがあります。 彼女は初期化(コンストラクター)と初期化解除(デストラクター)が必要です。 通常、初期化はメモリ領域のゼロ化に限定され、初期化解除中は何も行われません。







TSRMレイヤーは、リソースに一意のIDを提供します。 その後、TSRMから保護されたメモリ領域を返すために後で必要になるため、呼び出し元はこのIDを保存する必要があります。







新しいリソースを作成するTSRM機能:







 typedef struct { size_t size; ts_allocate_ctor ctor; ts_allocate_dtor dtor; int done; } tsrm_resource_type; TSRM_API ts_rsrc_id ts_allocate_id(ts_rsrc_id *rsrc_id, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor) { int i; tsrm_mutex_lock(tsmm_mutex); /*  id  */ *rsrc_id = id_count++; /*         */ if (resource_types_table_size < id_count) { resource_types_table = (tsrm_resource_type *) realloc(resource_types_table, sizeof(tsrm_resource_type)*id_count); if (!resource_types_table) { tsrm_mutex_unlock(tsmm_mutex); TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate storage for resource")); *rsrc_id = 0; return 0; } resource_types_table_size = id_count; } resource_types_table[(*rsrc_id)-1].size = size; resource_types_table[(*rsrc_id)-1].ctor = ctor; resource_types_table[(*rsrc_id)-1].dtor = dtor; resource_types_table[(*rsrc_id)-1].done = 0; /*        */ for (i=0; icount < id_count) { int j; p->storage = (void *) realloc(p->storage, sizeof(void *)*id_count); for (j=p->count; jstorage[j] = (void *) malloc(resource_types_table[j].size); if (resource_types_table[j].ctor) { resource_types_table[j].ctor(p->storage[j]); } } p->count = id_count; } p = p->next; } } tsrm_mutex_unlock(tsmm_mutex); return *rsrc_id; }
      
      





ご覧のとおり、この関数には相互排他ロック(相互排他ロック)が必要です。 実行の子スレッドで呼び出された場合(およびそれぞれのスレッドで呼び出された場合)、グローバルスレッドストレージ状態の操作が完了するまで他のスレッドをブロックします。







新しいリソースは動的配列resource_types_table[]



追加され、一意の識別子rsrc_id



を取得します。これは、リソースが追加されると増分されます。







実行中のリクエスト



これで、リクエストを処理する準備ができました。 各リクエストは、独自の実行スレッドで処理されることに注意してください。 リクエストが表示されるとどうなりますか? 各新しいリクエストの最初に、 ts_resource_ex()



関数がts_resource_ex()



ます。 現在の実行スレッドのIDを読み取り、このスレッドに割り当てられたリソース、つまり現在のスレッドのグローバルのメモリ領域を抽出しようとします。 リソースが検出されない場合(ストリームは新しい)、リソースはPHPの起動時に作成されたモデルに基づいて現在のストリームに割り当てられます。 これはallocate_new_resource()



を使用して行われます







 static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_T thread_id) { int i; TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Creating data structures for thread %x", thread_id)); (*thread_resources_ptr) = (tsrm_tls_entry *) malloc(sizeof(tsrm_tls_entry)); (*thread_resources_ptr)->storage = NULL; if (id_count > 0) { (*thread_resources_ptr)->storage = (void **) malloc(sizeof(void *)*id_count); } (*thread_resources_ptr)->count = id_count; (*thread_resources_ptr)->thread_id = thread_id; (*thread_resources_ptr)->next = NULL; /*           */ tsrm_tls_set(*thread_resources_ptr); if (tsrm_new_thread_begin_handler) { tsrm_new_thread_begin_handler(thread_id); } for (i=0; istorage[i] = NULL; } else { (*thread_resources_ptr)->storage[i] = (void *) malloc(resource_types_table[i].size); if (resource_types_table[i].ctor) { resource_types_table[i].ctor((*thread_resources_ptr)->storage[i]); } } } if (tsrm_new_thread_end_handler) { tsrm_new_thread_end_handler(thread_id); } tsrm_mutex_unlock(tsmm_mutex); }
      
      





拡張ローカルストレージキャッシュ



PHP 7の各拡張機能は、ローカルストレージでキャッシュを宣言できます。 これは、新しい実行スレッドが開始するたびに、各拡張機能が独自の実行スレッドのローカルストレージ領域を読み取り、グローバルアクセスにアクセスするたびにリポジトリのリストを反復処理しないことを意味します。 魔法はありません。これにはいくつかのことをする必要があります。







最初に、キャッシュをサポートしてPHPをコンパイルする必要があります。コンパイルコマンドラインで、 -DZEND_ENABLE_STATIC_TSRMLS_CACHE = 1入力します。 いずれにせよ、これはデフォルトで行われるべきです。 次に、拡張機能のグローバルを宣言するときに、マクロZEND_TSRMLS_CACHE_DEFINE()



使用します。







#define ZEND_TSRMLS_CACHE_DEFINE(); __thread void *_tsrm_ls_cache = ((void *)0);









ご覧のとおり、真のCグローバルは、特別な__thread



でのみ__thread



ます。 これは、スレッド固有の変数になることをコンパイラー伝えるためです。







次に、このvoid *リポジトリに、TSRMレベルでグローバル用に予約されたリポジトリのデータを入力する必要があります。 これを行うには、グローバルコンストラクターでZEND_TSRMLS_CACHE_UPDATE()



を使用できます。







 PHP_GINIT_FUNCTION(my_ext) { #ifdef ZTS ZEND_TSRMLS_CACHE_UPDATE(); #endif /* Continue initialization here */ } ```cpp   (macro expansion): ```#define ZEND_TSRMLS_CACHE_UPDATE() _tsrm_ls_cache = tsrm_get_ls_cache();```    pthread: ```#define tsrm_get_ls_cache pthread_getspecific(tls_key)``` ,    ,         —    : ```cpp #ifdef ZTS #define MY_G(v) (((my_globals *) (*((void ***) _tsrm_ls_cache))[((my_globals_id)-1)])->(v))
      
      





ご覧のとおり、MY_G()マクロを使用してグローバルにアクセスすると、ストリーム環境を使用しているときに拡張され、この拡張機能のID _tsrm_ls_cache



を使用して_tsrm_ls_cache



領域をチェックします。







画像







これまで見てきたように、各拡張機能はリソースであり、そのグローバル用のスペースを提供します。 ID. TSRM , /.







おわりに



— . , PHP : TLS, , — TSRM. , , . , PHP .







TSRM: -, , . , ZTS, . TSRM : , , .







, , (request-bound). - -, , servinf : , , -.








All Articles