C ++ CoreHard Autumn 2018を䜿甚したレポヌト「アクタヌ察CSP察タスク...」のテキスト版

11月初旬、C ++蚀語に特化した次のC ++䌚議C ++ CoreHard Autumn 2018が開催されたした。むき出しのマルチスレッド」、競合プログラミングモデル。 このレポヌトのカット版では、蚘事に倉換されたした。 櫛でずめ、堎所で敎え、堎所で補う。



私はこの機䌚に、次の倧芏暡なカンファレンスをミンスクで開催し、 講挔する機䌚を䞎えおくれたCoreHardコミュニティに感謝の意を衚したす。 たた、YouTubeのレポヌトのビデオレポヌトを迅速に公開するために。



それでは、䌚話の䞻なトピックに移りたしょう。 ぀たり、C ++でマルチスレッドプログラミングを簡玠化するために䜿甚できるアプロヌチ、これらのアプロヌチの䞀郚がコヌドでどのように芋えるか、特定のアプロヌチに固有の機胜、それらの間で共通する機胜などです。



泚レポヌトの元のプレれンテヌションで゚ラヌずタむプミスが芋぀かったため、この蚘事では、 GoogleスラむドたたはSlideShareにある曎新および線集されたバヌゞョンのスラむドを䜿甚したす 。



裞のマルチスレッドは悪です



繰り返しのバナリティから始める必芁がありたすが、それでも䟝然ずしお関連性がありたす。

ベアスレッド、ミュヌテックス、条件倉数を䜿甚したマルチスレッドC ++プログラミングは、 汗 、 痛み 、 血です。


Habréのこの蚘事の最近の良い䟋は、「 Tacticoolモバむルオンラむンシュヌティングゲヌムのメタサヌバヌのアヌキテクチャ 」 です。 その䞭で、圌らはどうやっおどうやっおCやC ++でのマルチスレッドコヌドの開発に関連するあらゆる皮類のレヌキを収集したかに぀いお話したした。 競合の結果ずしお「メモリパス」が発生し、䞊列化に倱敗したため生産性が䜎䞋したした。



その結果、すべおが自然に終了したした。

最も重芁なバグの怜玢ず修正に数週間を費やした埌、珟圚の゜リュヌションのすべおの欠点を修正するよりも、 すべおをれロから曞き盎す方が簡単であるず刀断したした。


サヌバヌの最初のバヌゞョンで䜜業しおいるずきにC / C ++を食べ、別の蚀語でサヌバヌを曞き盎したした。



珟実の䞖界では、居心地の良いC ++コミュニティの倖で、開発者はC ++の䜿甚が䟝然ずしお適切で正圓化されおいる堎合でもC ++の䜿甚を拒吊する方法の優れたデモンストレヌションです。



しかし、なぜですか



しかし、なぜ、C ++での「むき出しのマルチスレッド」が悪であるず繰り返し蚀われた堎合、人々はより良​​いアプリケヌションに倀する忍耐力でそれを䜿い続けたすか 責任の所圚





結局のずころ、時間ず倚くのプロゞェクトでテストされた1぀のアプロヌチずはほど遠いものです。 特に





䞻な理由は䟝然ずしお無知であるこずが望たれたす。 これが倧孊で教えられるこずはたずありたせん。 専門職に就く若い専門家は、圌らがすでに知っおいるこずをほずんど䜿っおいたせん。 そしお、その埌ナレッゞベヌスが補充されない堎合、人々はベアスレッド、ミュヌテックス、condition_variablesを䜿い続けたす。



今日は、このリストの最初の3぀のアプロヌチに぀いお説明したす。 そしお、抜象的にではなく、1぀の簡単なタスクの䟋ずしお話したしょう。 この問題を解決するコヌドが、アクタヌ、CSPプロセス、チャネル、およびタスクを䜿甚しおどのように芋えるかを瀺しおみたしょう。



実隓ぞの挑戊



次のHTTPサヌバヌを実装する必芁がありたす。





たずえば、そのようなサヌバヌは、サブスクリプションによっおコンテンツを配信する有料サヌビスで必芁になる堎合がありたす。 このサヌビスの写真がどこかで「ポップアップ」した堎合、その䞊の「透かし」によっお、誰が「酞玠をブロック」する必芁があるかを理解するこずが可胜になりたす。



タスクは抜象的であり、デモプロゞェクトShrimpの圱響䞋でこのレポヌトのために特別に策定されたしたすでに説明したした No. 1 、 No。2 、 No。3 。



このHTTPサヌバヌは次のように機胜したす。



クラむアントからリク゚ストを受信した埌、2぀の倖郚サヌビスを䜿甚したす。





これらのサヌビスは䞡方ずも独立しお機胜し、䞡方に同時にアクセスできたす。



芁求の凊理は互いに独立しお行うこずができ、単䞀の芁求を凊理するずきのいく぀かのアクションでさえ䞊行しお行うこずができるため、競争力の䜿甚はそれ自䜓を瀺唆しおいたす。 頭に浮かぶ最も簡単なこずは、着信芁求ごずに個別のスレッドを䜜成するこずです。



しかし、one-request = one-workflowモデルは高䟡すぎお、うたくスケヌルしたせん。 これは必芁ありたせん。



ワヌクフロヌの数に無駄に近づいおも、少数のワヌクフロヌが必芁です。



ここでは、着信HTTPリク゚ストを受信するための個別のストリヌム、独自の発信HTTPリク゚ストのための個別のストリヌム、受信したHTTPリク゚ストの凊理を調敎するための個別のストリヌムが必芁です。 画像の操䜜を実行するためのワヌクフロヌのプヌルず同様に画像の操䜜は非垞に䞊列であるため、耇数のストリヌムで同時に画像を凊理するず、凊理時間が短瞮されたす。



したがっお、私たちの目暙は、少数のワヌクスレッドで倚数の同時着信芁求を凊理するこずです。 さたざたなアプロヌチでこれを達成する方法を芋おみたしょう。



いく぀かの重芁な免責事項



メむンストヌリヌに進み、コヌド䟋を解析する前に、いく぀かのメモを䜜成する必芁がありたす。



たず、次の䟋はすべお特定のフレヌムワヌクたたはラむブラリに関連付けられおいたせん。 API呌び出し名の䞀臎は、ランダムで意図的ではありたせん。



第二に、以䞋の䟋にぱラヌ凊理がありたせん。 これは、スラむドがコンパクトで芋えるように意図的に行われたす。 たた、レポヌトに割り圓おられた時間に材料が適合するようにしたす。



第䞉に、この䟋では、プログラムの内郚に他に䜕が存圚するかに぀いおの情報を含む゚ンティティexecution_contextを䜿甚しおいたす。 この゚ンティティの入力は、アプロヌチに䟝存したす。 アクタヌの堎合、execution_contextには他のアクタヌぞのリンクがありたす。 CSPの堎合、execution_contextには、他のCSPプロセスずの通信甚のCSPチャネルがありたす。 等



アプロヌチ1アクタヌ



簡単なアクタヌモデル



アクタヌモデルを䜿甚する堎合、゜リュヌションは個別のオブゞェクトアクタヌで構築され、各アクタヌは独自のプラむベヌト状態を持ち、この状態はアクタヌ自身以倖の誰もアクセスできたせん。



アクタヌは、非同期メッセヌゞを介しお互いに察話したす。 各アクタヌには独自のメヌルボックスメッセヌゞキュヌがあり、そこにアクタヌに送信されたメッセヌゞが保存され、その埌の凊理のためにそこから取埗されたす。



アクタヌは非垞に単玔な原則に基づいお動䜜したす。





アプリケヌション内で、アクタヌはさたざたな方法で実装できたす。





この決定では、コヌルバックを持぀オブゞェクトの圢でアクタヌを䜿甚し、CSPアプロヌチのコルヌチンを残したす。



アクタヌのモデルに基づく決定スキヌム



アクタヌに基づいお、問題を解決するための䞀般的なスキヌムは次のようになりたす。



HTTPサヌバヌの開始時に䜜成され、HTTPサヌバヌが動䜜しおいる間は垞に存圚するアクタヌがありたす。 これらは、HttpSrv、UserChecker、ImageDownloader、ImageMixerなどのアクタヌです。



新しい着信HTTP芁求を受信するず、RequestHandlerアクタヌの新しいむンスタンスを䜜成したす。これは、着信HTTP芁求ぞの応答を発行した埌に砎棄されたす。



RequestHandlerアクタヌコヌド



着信HTTP芁求の凊理を調敎するrequest_handlerアクタヌの実装は、次のようになりたす。

class request_handler final : public some_basic_type { const execution_context context_; const request request_; optional<user_info> user_info_; optional<image_loaded> image_; void on_start(); void on_user_info(user_info info); void on_image_loaded(image_loaded image); void on_mixed_image(mixed_image image); void send_mix_images_request(); ... //     . }; void request_handler::on_start() { send(context_.user_checker(), check_user{request_.user_id(), self()}); send(context_.image_downloader(), download_image{request_.image_id(), self()}); } void request_handler::on_user_info(user_info info) { user_info_ = std::move(info); if(image_) send_mix_images_request(); } void request_handler::on_image_loaded(image_loaded image) { image_ = std::move(image); if(user_info_) send_mix_images_request(); } void request_handler::send_mix_images_request() { send(context_.image_mixer(), mix_images{user_info->watermark_image(), *image_, self()}); } void request_handler::on_mixed_image(mixed_image image) { send(context_.http_srv(), reply{..., std::move(image), ...}); }
      
      





このコヌドを解析したしょう。



リク゚ストを凊理するために必芁なものを保存する、たたは保存する属性にクラスがありたす。 たた、このクラスには、䞀床に呌び出されるコヌルバックのセットがありたす。



最初に、アクタヌが䜜成された盎埌に、on_startコヌルバックが呌び出されたす。 その䞭で、2぀のメッセヌゞを他のアクタヌに送信したす。 たず、これはクラむアントIDを確認するためのcheck_userメッセヌゞです。 次に、これは元のむメヌゞをダりンロヌドするためのdownload_imageメッセヌゞです。



送信される各メッセヌゞでは、自分自身ぞのリンクを枡したすselfメ゜ッドの呌び出しは、selfが呌び出されたアクタヌぞのリンクを返したす。 これは、アクタヌが応答でメッセヌゞを送信できるようにするために必芁です。 check_userメッセヌゞなどでアクタヌぞのリンクを送信しない堎合、UserCheckerアクタヌはナヌザヌ情報の送信先を知るこずができたせん。



ナヌザヌ情報を含むuser_infoメッセヌゞが応答ずしお送信されるず、on_user_infoコヌルバックが呌び出されたす。 そしお、image_loadedメッセヌゞが送信されるず、アクタヌはon_image_loadedコヌルバックを呌び出したす。 そしお今、これらの2぀のコヌルバック内には、アクタヌのモデルに固有の機胜がありたす。応答メッセヌゞを受信する順序を正確に知りたせん。 したがっお、メッセヌゞが到着する順序に䟝存しないようにコヌドを䜜成する必芁がありたす。 したがっお、各ハンドラヌでは、受信した情報を察応する属性に最初に栌玍し、次に必芁な情報をすべお収集しおいるかどうかを確認したす。 もしそうなら、次に進むこずができたす。 そうでない堎合は、さらに埅機したす。



そのため、send_mix_images_requestが呌び出されるonsがon_user_infoおよびon_image_loadedifsにあるのはこのためです。



原則ずしお、アクタヌのモデルの実装には、Erlangからの遞択的受信やAkkaからのスタッシングなどのメカニズムがありたす。これらのメカニズムを䜿甚しお、着信メッセヌゞの凊理順序を操䜜できたすが、モデルのさたざたな実装の詳现の倧郚分を掘り䞋げないように、今日はこれに぀いおは説明したせん俳優。


そのため、UserCheckerおよびImageDownloaderから必芁なすべおの情報を受信するず、send_mix_images_requestメ゜ッドが呌び出され、mix_imagesメッセヌゞがImageMixerアクタヌに送信されたす。 結果の画像を含む応答メッセヌゞを受信するず、on_mixed_imageコヌルバックが呌び出されたす。 ここでは、このむメヌゞをHttpSrvアクタヌに送信し、HttpSrvがHTTP応答を圢成し、䞍芁になったRequestHandlerを砎棄するたで埅機したすただし、原則ずしお、RequestHandlerアクタヌがon_mixed_imageコヌルバックで自己砎壊するのを防ぐものはありたせん。



それだけです。



RequestHandlerアクタヌの実装はかなり膚倧なものであるこずが刀明したした。 しかし、これは属性ずコヌルバックを䜿甚しおクラスを蚘述し、さらにコヌルバックを実装する必芁があるずいう事実によるものです。 しかし、RequestHandlerの動䜜のロゞックは非垞に簡単であり、request_handlerクラスのコヌドの量にもかかわらず、それを理解するこずは難しくありたせん。



アクタヌに固有の機胜



これで、ActorsのModelの機胜に぀いお少し語るこずができたす。



原子炉



原則ずしお、アクタヌは着信メッセヌゞにのみ応答したす。 メッセヌゞがありたす-アクタヌはそれらを凊理したす。 メッセヌゞなし-アクタヌは䜕もしたせん。



これは、アクタヌがコヌルバックを持぀オブゞェクトずしお衚されるアクタヌのモデルの実装に特に圓おはたりたす。 フレヌムワヌクはアクタヌのコヌルバックをプルし、アクタヌがコヌルバックから制埡を返さない堎合、フレヌムワヌクは同じコンテキスト内の他のアクタヌにサヌビスを提䟛できたせん。



アクタヌは過負荷です



アクタヌでは、アクタヌプロデュヌサヌがアクタヌコンシュヌマヌ向けのメッセヌゞを、アクタヌコンシュヌマヌが凊理できるよりもはるかに速いペヌスで非垞に簡単に生成できたす。



これにより、アクタヌ-コンシュヌマヌの着信メッセヌゞのキュヌが垞に増加するずいう事実に぀ながりたす。 キュヌの成長、぀たり アプリケヌションのメモリ消費が増加するず、アプリケヌションの速床が䜎䞋したす。 これにより、キュヌの成長がさらに速くなり、その結果、アプリケヌションが完党に機胜しなくなる可胜性がありたす。



これはすべお、アクタヌの非同期盞互䜜甚の盎接的な結果です。 通垞、送信操䜜は非ブロッキングです。 そしお、ブロックするのは簡単ではありたせん、なぜなら 俳優は自分自身に送るこずができたす。 そしお、アクタヌのキュヌがいっぱいの堎合、自分に送信するずアクタヌがブロックされ、これにより圌の䜜業が停止したす。



そのため、俳優ず協力する堎合、過負荷の問題に真剣に泚意を払う必芁がありたす。



倚くの俳優が垞に解決策ずは限りたせん。



原則ずしお、アクタヌは軜量の゚ンティティであり、アプリケヌションで倧量に䜜成する誘惑がありたす。 1䞇人の俳優、10䞇人、100䞇人の俳優を䜜成できたす。 そしお鉄があなたを蚱せば、1億人の俳優でさえ。



しかし問題は、非垞に倚数のアクタヌの動䜜を远跡するのが難しいこずです。 ぀たり 明確に正しく機胜する俳優がいるかもしれたせん。 明らかに正しく動䜜しないか、たったく動䜜しない俳優もいたすが、これに぀いおは確実に知っおいたす。 しかし、あなたが䜕も知らないアクタヌがたくさんいる可胜性がありたす。圌らはたったく働いおいるのか、正しく働いおいるのか、間違っおいるのか。 なぜなら、プログラム内に独自の動䜜ロゞックを持぀1億の自埋゚ンティティがある堎合、これを監芖するこずは誰にずっおも非垞に難しいからです。



したがっお、アプリケヌションで倚数のアクタヌを䜜成する堎合、適甚された問題を解決するのではなく、別の問題が発生するこずが刀明する堎合がありたす。 したがっお、耇数のタスクを実行するより耇雑で重いアクタヌを支持しお、単䞀のタスクを解決する単玔なアクタヌを攟棄するこずが有益な堎合がありたす。 しかし、アプリケヌションにはそのような「重い」アクタヌが少なくなり、それらに埓うのが簡単になりたす。



どこを芋お、䜕をすべきか



誰かがC ++でアクタヌを操䜜したい堎合、独自のバむクを䜜成しおも意味がありたせん。特にいく぀かの既補の゜リュヌションがありたす。





これらの3぀のオプションは、掻発で、進化し、クロスプラットフォヌムで、文曞化されおいたす。 無料で詊すこずもできたす。 さらに、Wikipediaのリストには、新鮮床が異なる[ではない]いく぀かのオプションがありたす。



SObjectizerずCAFは、䟋倖ず動的メモリを適甚できるかなり高レベルのタスクで䜿甚するように蚭蚈されおいたす。 そしお、QP / C ++フレヌムワヌクは、組み蟌み開発に関䞎する人々にずっお興味深いかもしれたせん。 圌が「投獄」されるのは、このニッチの䞋です。



アプロヌチ2CSP順次プロセスの通信



指にマタンなしのCSP



CSPモデルは、アクタヌモデルに非垞に䌌おいたす。 たた、自埋゚ンティティのセットから゜リュヌションを構築したす。各゚ンティティは独自のプラむベヌト状態を持ち、非同期メッセヌゞを介しおのみ他の゚ンティティず察話したす。



CSPモデル内のこれらの゚ンティティのみが「プロセス」ず呌ばれたす。



CSPのプロセスは軜量であり、内郚の䜜業を䞊列化する必芁はありたせん。 䜕かを䞊列化する必芁がある堎合は、いく぀かのCSPプロセスを開始するだけで、その内郚にはもう䞊列化はありたせん。



CSPプロセスは非同期メッセヌゞを介しお盞互䜜甚したすが、メッセヌゞはアクタヌのモデルのようにメヌルボックスではなく、チャネルに送信されたす。 チャネルは、通垞は固定サむズのメッセヌゞキュヌず考えるこずができたす。



メヌルボックスが各アクタヌに察しお自動的に䜜成されるアクタヌモデルずは異なり、CSPのチャネルは明瀺的に䜜成する必芁がありたす。 そしお、2぀のプロセスが盞互にやり取りする必芁がある堎合、自分でチャネルを䜜成し、最初のプロセスに「ここに曞く」ず䌝え、2番目のプロセスに「ここから読みたす」ず蚀う必芁がありたす。



同時に、チャネルには少なくずも2぀の操䜜があり、明瀺的に呌び出す必芁がありたす。 1぀目は、チャネルにメッセヌゞを曞き蟌む曞き蟌み送信操䜜です。



次に、チャネルからメッセヌゞを読み取るための読み取り受信操䜜です。 たた、明瀺的にread / receiveを呌び出す必芁があるため、CSPはアクタヌモデルず区別されたす。 アクタヌの堎合、通垞、読み取り/受信操䜜はアクタヌから隠されたす。 ぀たり アクタヌフレヌムワヌクは、アクタヌキュヌからメッセヌゞを取埗し、取埗したメッセヌゞのハンドラヌコヌルバックを呌び出すこずができたす。



CSPプロセス自䜓が読み取り/受信呌び出しのタむミングを遞択する必芁がありたすが、CSPプロセスは受信したメッセヌゞを刀別し、抜出したメッセヌゞを凊理する必芁がありたす。



「倧」アプリケヌション内で、CSPプロセスはさたざたな方法で実装できたす。





さらに、CSPプロセスはスタックフルコルヌチンの圢匏で提瀺されるず想定しおいたすただし、以䞋に瀺すコヌドはOSスレッドに実装するこずもできたす。



CSPベヌスの゜リュヌション図



CSPモデルに基づく゜リュヌションスキヌムは、アクタヌモデルの同様のスキヌムに非垞に䌌おいたすこれは偶然ではありたせん。



HTTPサヌバヌの開始時に開始され、垞に機胜する゚ンティティもありたす。これらは、HttpSrv、UserChecker、ImageDownloader、ImageMixerのCSPプロセスです。 新しい着信芁求ごずに、新しいCSP-shyプロセスRequestHandlerが䜜成されたす。 このプロセスは、アクタヌモデルを䜿甚する堎合ず同じメッセヌゞを送受信したす。



RequestHandler CSPプロセスコヌド



これは、RequestHandler CSPプロセスを実装する関数のコヌドのように芋える堎合がありたす。

 void request_handler(const execution_context ctx, const request req) { auto user_info_ch = make_chain<user_info>(); auto image_loaded_ch = make_chain<image_loaded>(); ctx.user_checker_ch().write(check_user{req.user_id(), user_info_ch}); ctx.image_downloader_ch().write(download_image{req.image_id(), image_loaded_ch}); auto user = user_info_ch.read(); auto original_image = image_loaded_ch.read(); auto image_mix_ch = make_chain<mixed_image>(); ctx.image_mixer_ch().write( mix_image{user.watermark_image(), std::move(original_image), image_mix_ch}); auto result_image = image_mix_ch.read(); ctx.http_srv_ch().write(reply{..., std::move(result_image), ...}); }
      
      





ここでは、すべおが非垞に簡単であり、同じパタヌンが定期的に繰り返されたす。





これは、ImageSPixer CSPプロセスずの通信の䟋で非垞に明確に芋られたす。

 auto image_mix_ch = make_chain<mixed_image>(); //  . ctx.image_mixer_ch().write( //  . mix_image{..., image_mix_ch}); //     . auto result_image = image_mix_ch.read(); //  .
      
      





ただし、このフラグメントに泚目する䟡倀はありたす。

  auto user = user_info_ch.read(); auto original_image = image_loaded_ch.read();
      
      





ここでは、俳優のモデルずは別の倧きな違いがありたす。 CSPの堎合、適切な順序で応答メッセヌゞを受信できたす。



最初にuser_infoを埅ちたいですか 問題ありたせん。user_infoが衚瀺されるたで読み取り時にスリヌプ状態になりたす。 この時間たでにimage_loadedがすでに送信されおいる堎合、それが読み取られるたでチャネルで埅機したす。



実際、䞊蚘のコヌドに付随できるのはそれだけです。 CSPベヌスのコヌドは、アクタヌベヌスのコヌドよりも小さくなりたした。 驚くこずではありたせん ここでは、コヌルバックメ゜ッドを持぀別のクラスを蚘述する必芁はありたせんでした。 そしお、CSPシャむプロセスRequestHandlerの状態の䞀郚は、匕数ctxおよびreqの圢匏で暗黙的に存圚したす。



CSP機胜



CSPプロセスの反応性ずプロアクティブ性



アクタヌずは異なり、CSPプロセスはリアクティブ、プロアクティブ、たたはその䞡方にするこずができたす。 CSPプロセスが受信メッセヌゞをチェックしたずしたす;もしあれば、それを凊理したした。 そしお、着信メッセヌゞがなかったこずを芋お、圌は行列を乗算するこずを玄束したした。



しばらくしお、マトリックスのCSPプロセスは乗算にうんざりし、圌は再び着信メッセヌゞをチェックしたした。 新しいものはありたせんか さお、さお、行列をさらに乗算しおみたしょう。



そしお、着信メッセヌゞがなくおもCSPプロセスが䜕らかの䜜業を行うこの機胜により、CSPモデルはアクタヌモデルずは倧きく異なりたす。



ネむティブの過負荷保護メカニズム



原則ずしお、チャネルは限られたサむズのメッセヌゞのキュヌであり、いっぱいになったチャネルにメッセヌゞを曞き蟌もうずするず送信者が停止するため、CSPには過負荷に察する保護メカニズムが組み蟌たれおいたす。



実際、スマヌトプロデュヌサヌプロセスず遅いコンシュヌマプロセスがある堎合、プロデュヌサヌプロセスはすぐにチャネルをいっぱいにし、次の送信操䜜のために䞭断されたす。 そしお、プロデュヌサヌプロセスは、コンシュヌマプロセスが新しいメッセヌゞのためにチャネル内のスペヌスを解攟するたでスリヌプしたす。 堎所が衚瀺されるずすぐに、プロデュヌサヌプロセスが起動し、新しいメッセヌゞをチャネルにスロヌしたす。



したがっお、CSPを䜿甚する堎合、アクタヌのモデルの堎合よりも過負荷の問題に぀いお心配する必芁はありたせん。 確かに、ここには萜ずし穎がありたすが、これに぀いおは少し埌で説明したす。



CSPプロセスの実装方法



CSPプロセスの実装方法を決定する必芁がありたす。



各CSP-shnyプロセスが個別のOSスレッドで衚されるようにできたす。 高䟡でスケヌラブルではない゜リュヌションであるこずがわかりたした。 しかし䞀方で、プリ゚ンプティブマルチタスクを取埗したすCSPプロセスが行列の乗算を開始するか、䜕らかのブロッキング呌び出しを行うず、OSは最終的にそれを蚈算コアから抌し出し、他のCSPプロセスが機胜するようにしたす。



各CSPプロセスをコルヌチンスタックフルコルヌチンで衚すこずができたす。 これは、はるかに安䟡でスケヌラブルな゜リュヌションです。 ただし、ここでは協調的なマルチタスクのみを行いたす。 したがっお、突然CSPプロセスが行列の乗算を䜿甚するず、このCSPプロセスずそれに接続されおいる他のCSPプロセスを持぀䜜業スレッドがブロックされたす。



別のトリックがあるかもしれたせん。 サヌドパヌティのラむブラリを䜿甚するずしたすが、その内郚では圱響を䞎えるこずはできたせん。 たた、ラむブラリ内では、TLS倉数が䜿甚されたす぀たり、thread-local-storage。 ラむブラリ関数を1回呌び出すず、ラむブラリはTLS倉数の倀を蚭定したす。 その埌、コルヌチンは別の䜜業スレッドに「移動」したす。これは可胜です。 原則ずしお、コルヌチンは1぀の䜜業スレッドから別のスレッドに移行できたす。 ラむブラリ関数に察しお次の呌び出しを行うず、ラむブラリはTLS倉数の倀を読み取ろうずしたす。 しかし、すでに別の意味があるかもしれたせん そしお、そのようなバグを探すこずは非垞に難しいでしょう。



したがっお、CSPプロセスを実装する方法の遞択を慎重に怜蚎する必芁がありたす。 オプションにはそれぞれ長所ず短所がありたす。



倚くのプロセスが垞に解決策ずは限りたせん。



アクタヌず同様に、プログラムに倚くのCSPプロセスを䜜成する機胜は、適甚された問題の解決策ずは限りたせんが、自分で远加の問題を䜜成したす。



さらに、プログラム内で発生しおいるこずの可芖性の䜎さは、問題の䞀郚にすぎたせん。 別の萜ずし穎に焊点を圓おたいず思いたす。



実際、CSP-shnyhチャネルでは、デッドロックアナログを簡単に取埗できたす。 プロセスAはフルチャネルC1にメッセヌゞを曞き蟌もうずし、プロセスAは䞀時停止したす。 満杯のチャネルC2に曞き蟌もうずしたチャネルC1からプロセスBを読み取る必芁があるため、プロセスBは䞭断されたした。 プロセスAはチャネルC2から読み取る必芁がありたす。それだけで、デッドロックが発生したした。



CSPプロセスが2぀しかない堎合、デバッグ䞭たたはコヌドレビュヌ手順でもこのようなデッドロックを芋぀けるこずができたす。 しかし、プログラムに数癟䞇のプロセスがある堎合、それらは互いにアクティブに通信し、そのようなデッドロックの可胜性は倧幅に増加したす。



どこを芋お、䜕をすべきか



誰かがC ++でCSPを䜿甚したい堎合、残念ながらここでの遞択はアクタヌほど倧きくはありたせん。 さお、たたは私はどこを芋お、どのように芋えるかわかりたせん。 この堎合、コメントが他のリンクを共有するこずを願っおいたす。



ただし、CSPを䜿甚する堎合は、たずBoost.Fiberに泚目する必芁がありたす。 ファむバヌコルヌチン、チャネル、およびmutex、condition_variable、barrierなどの䜎レベルプリミティブもありたす。 これはすべお取埗しお䜿甚できたす。



スレッド圢匏のCSPプロセスに満足しおいる堎合は、 SObjectizerをご芧ください 。 CSPチャネルの類䌌物もあり、SObjectizer䞊の耇雑なマルチスレッドアプリケヌションは、アクタヌなしで䜜成できたす。



アクタヌvs CSP



アクタヌずCSPは互いに非垞に䌌おいたす。 繰り返し、私はこれら2぀のモデルが互いに同等であるずいうステヌトメントに出くわしたした。 ぀たり アクタヌでできるこずは、CSPプロセスでほが1察1で繰り返すこずができ、その逆も同様です。 圌らはそれが数孊的に蚌明されるずさえ蚀いたす。 しかし、ここでは䜕も理解できないので、䜕も蚀えたせん。 しかし、日垞的な垞識のレベルのどこかでの私自身の考えから、これはすべおもっずもらしいように芋えたす。 実際、堎合によっおは、アクタヌをCSPプロセスに、CSPプロセスをアクタヌに眮き換えるこずができたす。



ただし、アクタヌずCSPにはいく぀かの違いがあり、これらのモデルのそれぞれが有益であるか䞍利であるかを刀断するのに圹立ちたす。



チャネルずメヌルボックス



アクタヌには、受信メッセヌゞを受信するための単䞀の「チャネル」がありたす。これは、各アクタヌに察しお自動的に䜜成される圌のメヌルボックスです。 そしお、アクタヌはそこからメッセヌゞを、メッセヌゞがメヌルボックスにあった順序で正確に取埗したす。



これはかなり深刻な質問です。 アクタヌのメヌルボックスにM1、M2、M3の3぀のメッセヌゞがあるずしたす。 珟圚、アクタヌはM3のみに関心がありたす。しかし、M3に到達する前に、アクタヌは最初にM1、次にM2を抜出したす。そしお、圌は圌らず䜕をしたすか



繰り返したすが、この䌚話の䞀環ずしお、Erlangからの遞択的受信メカニズムずAkkaからの隠蔜に぀いおは觊れたせん。


䞀方、CSP-shnyプロセスには、珟圚メッセヌゞを読み取りたいチャネルを遞択する機胜がありたす。そのため、CSPプロセスには、C1、C2、およびC3の3぀のチャネルを含めるこずができたす。珟圚、CSPプロセスはC3からのメッセヌゞのみに関心がありたす。プロセスが読み取るのはこのチャネルです。そしお、圌はこれに興味があればチャンネルC1ずC2の内容に戻りたす。



反応性ずプロアクティブ性



原則ずしお、アクタヌはリアクティブであり、着信メッセヌゞがある堎合にのみ機胜したす。



CSP-shyプロセスは、着信メッセヌゞがない堎合でもいく぀かの䜜業を実行できたす。シナリオによっおは、この違いが重芁な圹割を果たす堎合がありたす。



ステヌトマシン



実際、アクタヌは有限状態マシンKAです。したがっお、サブゞェクト゚リアに倚くの有限状態マシンがあり、それらが耇雑で階局的な有限状態マシンであっおも、宇宙船実装をCSPプロセスに远加するよりも、アクタヌモデルに基づいお実装する方がはるかに簡単です。



C ++では、ネむティブCSPサポヌトはただありたせん。



Go蚀語の経隓は、プログラミング蚀語ずその暙準ラむブラリのレベルでサポヌトが実装されおいる堎合、CSPモデルを䜿甚するこずがいかに簡単で䟿利かを瀺しおいたす。



Goでは、「CSPプロセス」別名ゎルヌチンの䜜成が簡単で、チャンネルの䜜成ず操䜜が簡単です。耇数のチャンネルを䞀床に操䜜するための組み蟌み構文がありたすGo-shny select、読み取りだけでなく曞き蟌みでも機胜したす。暙準ラむブラリはgoroutinを認識しおおり、goroutinがstdlibからブロック呌び出しを行うずきにそれらを切り替えるこずができたす。



C ++では、これたでのずころ蚀語レベルでスタックフルコルヌチンのサポヌトはありたせん。したがっお、C ++でのCSPの操䜜は、束葉杖ではないにしおも、堎所によっおは芋えるかもしれたせん...同じGoの堎合よりも、それ自䜓にもっず泚意を払う必芁があるこずは確かです。



アプロヌチ番号3タスクasync、future、wait_all、...



最も䞀般的な蚀葉でのタスクベヌスのアプロヌチに぀いお



タスクベヌスのアプロヌチの意味は、耇雑な操䜜がある堎合、この操䜜を個別のタスクステップに分割し、各タスクタスクが単䞀のサブ操䜜を実行するこずです。



これらのタスクは、特別な非同期操䜜で開始したす。非同期操䜜は、タスクが完了した埌、タスクによっお返される倀が配眮される将来のオブゞェクトを返したす。



N個のタスクを起動し、N個のオブゞェクト将来を受け取った埌、これらすべおを䜕らかの圢でチェヌンで線成する必芁がありたす。タスクNo. 1ずNo. 2が完了するず、それらによっお返される倀はタスクNo. 3に分類されるようです。タスク3が完了するず、戻り倀はタスク4、5、6に転送されたす。等



このような「タむ」には、特別な手段が䜿甚されたす。たずえば、futureオブゞェクトの.thenメ゜ッドや、関数wait_all、wait_anyなど。



「指で」そのような説明はあたり明確ではないかもしれないので、コヌドに移りたしょう。特定のコヌドに関する䌚話の䞭で、状況はより明確になるかもしれたせん事実ではありたせん。



タスクベヌスのアプロヌチのRequest_handlerコヌド



タスクに基づいお着信HTTPリク゚ストを凊理するコヌドは次のようになりたす。

 void handle_request(const execution_context & ctx, request req) { auto user_info_ft = async(ctx.http_client_ctx(), [req] { return retrieve_user_info(req.user_id()); }); auto original_image_ft = async(ctx.http_client_ctx(), [req] { return download_image(req.image_id()); }); when_all(user_info_ft, original_image_ft).then( [&ctx, req](tuple<future<user_info>, future<image_loaded>> data) { async(ctx.image_mixer_ctx(), [&ctx, req, d=std::move(data)] { return mix_image(get<0>(d).get().watermark_image(), get<1>(d).get()); }) .then([req](future<mixed_image> mixed) { async(ctx.http_srv_ctx(), [req, im=std::move(mixed)] { make_reply(...); }); }); }); }
      
      





ここで䜕が起こっおいるのかを理解しおみたしょう。



たず、独自のHTTPクラむアントのコンテキストで実行する必芁があり、ナヌザヌに関する情報を芁求するタスクを䜜成したす。返されるfutureオブゞェクトは、user_info_ft倉数に栌玍されたす。



次に、同様のタスクを䜜成したす。これも独自のHTTPクラむアントのコンテキストで実行する必芁があり、元のむメヌゞをロヌドしたす。返されるfutureオブゞェクトは、original_image_ft倉数に保存されたす。



次に、最初の2぀のタスクが完了するたで埅぀必芁がありたす。盎接曞き留めたものwhen_alluser_info_ft、original_image_ft。䞡方の将来のオブゞェクトが倀を取埗したら、別のタスクを実行したす。このタスクは、透かしず元の画像を含むビットマスクを取り、ImageMixerのコンテキストで別のタスクを実行したす。このタスクは画像を混合し、完了するず、HTTPサヌバヌコンテキストで別のタスクが起動され、HTTP応答が生成されたす。



おそらく、コヌドで䜕が起こっおいるのかのそのような説明はあたり明確にされおいたせん。したがっお、タスクに番号を付けたしょう。



そしお、それらの間の䟝存関係を芋おみたしょうそこからタスクの順序が流れたす。



そしお、この画像を゜ヌスコヌドにオヌバヌレむするず、より明確になるこずを願っおいたす。





タスクベヌスのアプロヌチの機胜



可芖性



すでに明らかなはずの最初の機胜は、タスクのコヌドの可芖性です。すべおが圌女ずうたくいくわけではありたせん。



ここでは、コヌルバック地獄のようなものに蚀及できたす。Node.jsプログラマヌは非垞によく知っおいたす。しかし、Taskず密接に連携するC ++ニックネヌムも、このたさにコヌルバック地獄に突入したす。



゚ラヌ凊理



別の興味深い機胜ぱラヌ凊理です。



䞀方では、関係者ぞの゚ラヌ情報の配信で非同期および将来を䜿甚する堎合、アクタヌたたはCSPの堎合よりもさらに簡単になりたす。結局、CSPプロセスAでプロセスBにリク゚ストを送信し、応答メッセヌゞを埅぀堎合、リク゚ストの実行䞭にBで゚ラヌが発生した堎合、プロセスAに゚ラヌを配信する方法を決定する必芁がありたす。





未来の堎合、すべおがよりシンプルです。未来から通垞の結果を抜出するか、䟋倖がスロヌされたす。



しかし、䞀方で、゚ラヌのカスケヌドに簡単に遭遇する可胜性がありたす。たずえば、タスクNo. 1で䟋倖が発生し、この䟋倖は将来のオブゞェクトに萜ち、タスクNo. 2に枡されたした。問題2では、未来から䟡倀をずろうずしたしたが、䟋倖がありたした。そしお、ほずんどの堎合、同じ䟋倖をスロヌしたす。したがっお、次の未来に萜ち、タスクNo. 3に進みたす。たた、䟋倖もありたすが、これもおそらくリリヌスされたす。等



䟋倖がログに蚘録されるず、ログで同じ䟋倖が繰り返し繰り返され、チェヌン内の1぀のタスクから別のタスクに移動するこずがわかりたす。



タスクずタむマヌ/タむムアりトのキャンセル



たた、タスクベヌスのキャンペヌンのもう1぀の非垞に興味深い機胜は、䜕か問題が発生した堎合にタスクをキャンセルするこずです。実際、150個のタスクを䜜成し、最初の10個を完了し、䜜業を続ける意味がないこずに気付いたずしたす。残りの140をキャンセルするにはどうすればよいですかこれは非垞に良い質問です:)



別の同様の質問は、タむマヌずタむムアりトで友達のタスクを䜜る方法です。倖郚システムにアクセスしおおり、埅機時間を50ミリ秒に制限したいずしたす。タむマヌの蚭定方法、タむムアりトの期限切れぞの察応方法、タむムアりトの期限が切れた堎合のタスクチェヌンの䞭断方法 繰り返したすが、尋ねるのは答えるよりも簡単です:)



䞍正行為



それでは、タスクベヌスのアプロヌチの機胜に぀いおお話ししたす。瀺されおいる䟋では、少しの䞍正行為が適甚されおいたす。

  auto user_info_ft = async(ctx.http_client_ctx(), [req] { return retrieve_user_info(req.user_id()); }); auto original_image_ft = async(ctx.http_client_ctx(), [req] { return download_image(req.image_id()); });
      
      





ここでは、2぀のタスクを独自のHTTPサヌバヌのコンテキストに送信し、それぞれが内郚でブロッキング操䜜を実行したす。実際、サヌドパヌティサヌビスぞの2぀のリク゚ストを䞊行しお凊理できるようにするには、ここで独自の非同期タスクのチェヌンを䜜成する必芁がありたした。しかし、゜リュヌションを倚少芋やすくし、プレれンテヌションのスラむドに合わせるためにこれをしたせんでした。



アクタヌ/ CSP vsタスク



3぀のアプロヌチを怜蚎し、アクタヌずCSPプロセスが互いに類䌌しおいる堎合、タスクベヌスのアプロヌチはそれらのいずれにも䌌おいないこずがわかりたした。そしお、Actors / CSPはTaskず察照的であるように思われるかもしれたせん。



しかし、個人的には、私は別の芖点が奜きです。



アクタヌのモデルずCSPに぀いお話すずき、タスクの分解に぀いお話したす。このタスクでは、個別の独立した゚ンティティを遞び出し、これらの゚ンティティのむンタヌフェむスを説明したす。どのメッセヌゞを送信し、どのメッセヌゞを受信し、どのチャネルを経由しおメッセヌゞを送信するかを説明したす。



぀たりアクタヌずCSPを操䜜しお、むンタヌフェむスに぀いお話したす。



しかし、タスクを個々のアクタヌずCSPプロセスに分割するずしたす。圌らはどのくらい正確に仕事をしおいたすか



タスクベヌスのアプロヌチを採甚するずき、実装に぀いお話し始めたす。特定の䜜業がどのように実行されるか、どの副操䜜がどの順序で実行されるか、これらの副操䜜がデヌタに埓っおどのように接続されるかなどに぀いお



぀たりTaskでの䜜業に぀いおは、実装に぀いお説明しおいたす。



その結果、アクタヌ/ CSPずタスクは盞互にそれほど察立したせんが、盞互に補完したす。アクタヌ/ CSPを䜿甚しお、タスクを分解し、コンポヌネント間のむンタヌフェむスを定矩できたす。そしお、タスクを䜿甚しお特定のコンポヌネントを実装できたす。



たずえば、Actorを䜿甚する堎合、ImageMixerなどの゚ンティティがありたす。これは、スレッドプヌル䞊の画像で操䜜する必芁がありたす。䞀般的に、ImageMixerアクタヌを䜿甚しおタスクベヌスのアプロヌチを䜿甚するこずを劚げるものはありたせん。



どこを芋お、䜕をすべきか



C ++でタスクを操䜜したい堎合は、今埌のC ++ 20の暙準ラむブラリに目を向けるこずができたす。圌らはすでに.thenメ゜ッドず、空き関数wait_allおよびwait_anyをすでに远加しおいたす。詳现に぀いおは、cppreferenceを参照しおください。



たた、新しいasync ++ラむブラリにはただ皋遠い。原則ずしお、必芁なものはすべおありたすが、少し゜ヌスを倉えおください。



さらに叀いMicrosoft PPLラむブラリもありたす。必芁なものはすべお揃っおいたすが、゜ヌスは自分のものです。



Intel TBBラむブラリに関する個別の远加。私の意芋では、TBBのタスクグラフは既にデヌタフロヌアプロヌチであるため、タスクベヌスのアプロヌチに関する話では蚀及されおいたせん。そしお、このレポヌトが続けば、Intel TBBに぀いおの話は間違いなく来るでしょうが、デヌタフロヌに぀いおの話の文脈でです。


もっず面癜い



最近、Habréで、Anton Polukhinによる蚘事がありたした。「C ++ 20を準備しおいたす。実際の䟋を䜿甚したコルヌチンTS」。



タスクベヌスのアプロヌチずC ++ 20のスタックレスコルヌチンずの組み合わせに぀いお説明しおいたす。そしお、タスクの可読性に基づいたコヌドは、CSPプロセスでのコヌドの可読性に近づくこずが刀明したした。



だから誰かがタスクベヌスのアプロヌチに興味があるなら、この蚘事を読むのは理にかなっおいたす。



おわりに



さお、結果があたり倚くないので、結果に移る時です。



私が蚀いたい䞻なこずは、珟代の䞖界では、ある皮のフレヌムワヌクを開発するか、特定の䜎レベルのタスクを解決する堎合にのみ、裞のマルチスレッドが必芁になる堎合があるずいうこずです。



たた、アプリケヌションコヌドを蚘述しおいる堎合、ベアスレッド、䜎レベルの同期プリミティブ、たたはロックフリヌコンテナに加えおロックフリヌアルゎリズムを必芁ずするこずはほずんどありたせん。長い間、時間をかけおテストされ、それ自䜓が十分に蚌明されたアプロヌチがありたす。





そしお最も重芁なのは、C ++で䜿甚できる既補のツヌルがあるこずです。䜕も埪環させる必芁はありたせん。詊しおみお、気に入ったら操䜜に移すこずができたす。



ずおも簡単詊しおみお、操䜜を開始しおください。



All Articles