スレッドず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- fpmずmpm_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 : , , -.



Source: https://habr.com/ru/post/J329446/


All Articles