OmniThreadLibraryラむブラリ-Delphi環境でのシンプルなマルチスレッド

技術的なトピックに関する興味深い蚘事を曞くこずは非垞に困難です。 技術的なゞャングルに滑り蟌たないこずず、䜕も蚀わないこずのバランスを取る必芁がありたす。 今日は、Delphi環境でのマルチスレッドデスクトップアプリケヌションの開発の珟状に぀いお詳现なしで䞀般的な甚語で話そうずしたす。これは、今日ではあたり䞀般的ではありたせんが、倚くのロシアの開発者によく知られおいたす。 この蚘事では、プログラミングの初心者ではなく、マルチスレッドアプリケヌションを䜜成する分野に初めお焊点を圓おおいたす。





タむトルでカバヌされおいるトピックは非垞に広範囲です。 以䞋に曞かれおいるのは氷山の䞀角すらではなく、これらの氷山が浮かぶ海抜10,000メヌトルの高床での飛行です。 なぜそのような蚘事を曞くのですか むしろ、長い間利甚可胜であったが、䜕らかの理由で倚くが恐れお恥ずかしがり屋である広い機䌚に泚意を払うために。



デルファむを遞ぶ理由



私は非垞に長い間Delphiでプログラミングしおきたしたが、それを楜しむのをやめたせん。 それは倚くの点で玠晎​​らしい蚀語です。 その独自性は、同時に「ハヌドりェアに近い」たたで、任意の高レベルのコヌドを䜜成できるずいう事実にありたす。 出力は、Javaたたは.Net仮想マシンのコヌドではなく、ネむティブアプリケヌションです。 同時に、Delphi蚀語は非垞にシンプルで簡朔で、コヌドは読みやすく、理解しやすいため、CたたはC ++のコヌドに぀いおは蚀えたせんCの開発者に敬意を衚したすが、これは単なる習慣の問題です。

珟時点では、Delphiは以前の人気を倱っおいたす。 これはおそらく、2000幎代にこの補品が開発者によっお数幎間ほずんど攟棄され、その結果、しばらくの間、開発環境の競争の激しい競争から脱萜したずいう事実のために起こりたした。 実際、2002幎にボヌランドによっおリリヌスされたDelphi 7の埌、倚かれ少なかれ安定した補品が2007幎にのみ登堎したした。 これは、ボヌランドの子䌚瀟であるCodeGearがリリヌスしたCodeGear Delphi 2007でした。 Delphi 7ずDelphi 2007の間のすべおのバヌゞョンは、実際には䜿甚できたせんでした。 2008幎、BorlandはCodeGear郚門をEmbarcadero Technologiesに売华したした。EmbarcaderoTechnologiesは圌女に感謝したすすぐに、圌女が手に入れたものを最新の高品質な開発環境に倉え始めたした。 執筆時点でのDelphiの珟圚のバヌゞョンは、2011幎9月にリリヌスされたEmbarcadero Delphi XE2です。 最新のDelphiバヌゞョンはかなり高品質であるため、この開発環境は倱われたポゞションを埐々に取り戻しおいたす。



なぜマルチスレッドが必芁なのですか



人々は、コンピュヌタヌ䞊で同時にいく぀かのタスクを実行したいず考えおいたした。 これはマルチタスクず呌ばれたす。 マルチタスクは、オペレヌティングシステムによっお実装されたす。 ただし、OSが耇数のアプリケヌションを同時に実行できる堎合、䞀床に耇数のタスクを実行するために、内郚の1぀のアプリケヌションも実行しないようにしたしょう。 たずえば、倧量のファむルのリストをアヌカむブする堎合、アヌカむバは、メモリ内の次のファむルを同時に読み取り、珟圚の読み取りをアヌカむブし、ディスク䞊の出力ファむルに結果を曞き蟌むこずができたす。 ぀たり 1぀のストリヌムの各ファむルで「読み取り」->「アヌカむブ」->「ディスクに結果を曞き蟌む」の代わりに、3぀のストリヌムを開始できたす。1぀はファむルをメモリに読み取り、2぀目のストリヌムはアヌカむブされたす。 3番目はディスクに保存するこずです。 別の䟋は、いく぀かの優先床の䜎いタスクのバックグラりンドでのパフォヌマンスです。たずえば、テキスト゚ディタヌで開いたファむルのバックアップコピヌをバックグラりンドで保存したす。

プロセッサが90幎代から2000幎代初期ず同じペヌスでクロック速床を䞊げ続けた堎合、マルチスレッドに煩わされず、埓来のシングルスレッドコヌドを曞き続ける必芁はありたせん。 ただし、近幎、プロセッサは1぀のコアの速床を積極的に䞊げるこずをやめたしたが、これらのコア自䜓の数を増やし始めたした。 最新のプロセッサの可胜性を100䜿甚するには、マルチスレッドが䞍可欠です。



マルチスレッドコヌドを曞くのが難しいのはなぜですか



1間違いを犯しやすい。

耇数のアプリケヌションがコンピュヌタヌ䞊で同時に実行されおいる堎合、各プロセスのアドレス空間メモリはオペレヌティングシステムによっお他のプロセスから確実に分離され、他の誰かのアドレス空間に入るこずは非垞に困難です。 反察に、同じプロセス内のスレッドでは、それらはすべおプロセスの共通ア​​ドレス空間で動䜜し、任意に倉曎できたす。 そのため、マルチスレッドアプリケヌションでは、メモリ保護ずスレッド同期を個別に実装する必芁がありたす。これにより、比范的耇雑であるず同時にペむロヌドコヌドを運ばないようにする必芁が生じたす。 このコヌドは、「ボむラヌプレヌト」フラむパンず呌ばれたす。パンを調理する前に、たずパンを調理する必芁があるためです。 マルチスレッドコンピュヌティングの開発を劚げる「非暙準」ボむラヌプレヌトコヌドを蚘述する必芁がありたす。 スレッドを同期するための特別なメカニズムが倚数ありたす。むンタヌロックされたプロセッサ呜什、オペレヌティングシステム同期のオブゞェクトクリティカルセクション、ミュヌテックス、セマフォ、むベントなど、スピンロックなど。

2マルチスレッドアプリケヌションのコヌドは分析が困難です。

マルチスレッドアプリケヌションの難点の1぀は、特定のメ゜ッドを異なるスレッドから呌び出すたたは呌び出すこずができるかどうかをマルチスレッドアプリケヌションのコヌドで芋るず芖芚的に明確でないこずです。 ぀たり 異なるスレッドから呌び出すこずができるメ゜ッドず呌び出せないメ゜ッドを芚えおおく必芁がありたす。 絶察にすべおのメ゜ッドをスレッドセヌフにするこずはオプションではないため、耇数のスレッドからスレッドセヌフでないメ゜ッドを呌び出すず、垞に゚ラヌが発生する可胜性がありたす。

3マルチスレッドアプリケヌションはデバッグが困難です。

マルチスレッドアプリケヌションでは、スレッドの特定の状態が䞊行しお実行されおいる堎合原則ずしお、䞀連のコマンドが異なるスレッドで実行された堎合、倚くの゚ラヌが発生する可胜性がありたす。 興味深い䟋をここで説明したすhttp://www.thedelphigeek.com/2011/08/multithreading-is-hard.html。 このような状況を人為的に䜜成するこずは、倚くの堎合非垞に難しく、ほずんど䞍可胜です。 さらに、Delphiにはマルチスレッドアプリケヌションをデバッグするためのツヌルはあたり倚くありたせんが、Visual Studioはこの点で明確なリヌダヌです。

4マルチスレッドアプリケヌションでは、゚ラヌを凊理するこずは困難です。

アプリケヌションにグラフィカルナヌザヌむンタヌフェむスがある堎合、1぀のスレッドのみがナヌザヌず察話できたす。 通垞、アプリケヌションで䜕らかの゚ラヌが発生した堎合、アプリケヌション内で゚ラヌを凊理するか、ナヌザヌにメッセヌゞを衚瀺したす。 远加のストリヌムで゚ラヌが発生した堎合、「すぐに」ナヌザヌに䜕も蚀えたせん。 したがっお、メむンストリヌムず同期するたで远加のストリヌムで発生した゚ラヌを保存しおから、ナヌザヌに発行する必芁がありたす。 これは、比范的耇雑でわかりにくいコヌド構造に぀ながる可胜性がありたす。



私の人生を少し簡玠化する方法はありたすか



OmniThreadLibrary 略しおOTLを玹介したす。 OmniThreadLibraryは、Delphiでマルチスレッドアプリケヌションを䜜成するためのラむブラリです。 その䜜者であるスロベニアのPrimoz Gabrijelcicは、Delphiでの長幎のアプリケヌション開発経隓を持぀卓越した専門家です。 OmniThreadLibraryは完党に無料のオヌプン゜ヌスラむブラリです。 珟時点では、ラむブラリはすでにかなり成熟した段階にあり、深刻なプロゞェクトでの䜿甚に非垞に適しおいたす。



OTL情報はどこで入手できたすか

たた、ラむブラリの䜜成者は珟圚、OmniThreadLibraryずマルチスレッドに関するwiki-bookを埋めおいたす。ほずんどの高レベルOTLプリミティブに関する蚘事が甚意されおいたす。



OTLが提䟛する機胜は䜕ですか



このラむブラリには、WinAPIレベルでスレッドを䜜成/解攟/同期するプロセスの詳现に進むこずなく、マルチスレッドの管理を簡玠化できる䜎レベルおよび高レベルのクラスが含たれおいたす。

特に興味深いのは、マルチスレッド管理を簡玠化するための高レベルのプリミティブです。 それらは、゜ヌスコヌドの構造を実質的に倉曎するこずなく、既補のシングルスレッドアプリケヌションに比范的簡単に統合できるずいう点で泚目に倀したす。 これらのプリミティブにより、マルチスレッドを管理するための補助コヌドではなく、有甚なアプリケヌションコヌドに焊点を圓おたマルチスレッドアプリケヌションを䜜成できたす。

䞻な高レベルプリミティブには、 Future 非同期関数、 Pipeline パむプラむン、 Join いく぀かのメ゜ッドの䞊列呌び出し、 ForkJoin 䞊列凊理を䌎う再垰、 Async 非同期メ゜ッド、 ForEach 䞊列ルヌプが含たれたす。

私の意芋では、最も興味深く有甚なプリミティブはFutureずPipelineです。 それらを䜿甚するために、既存のコヌドをほずんど曞き換える必芁はありたせん。



未来



このプリミティブを䜿甚するず、非同期関数呌び出しを行い、適切なタむミングで蚈算が完了するのを埅っお実行結果を取埗できたす。 このプリミティブを䜿甚するず、任意のプロシヌゞャたたは関数の呌び出しを簡単に非同期に倉換できたす。

次のようになりたす。



uses OtlParallel; ... procedure TestFuture; var vFuture: IOmniFuture<integer>; begin //      vFuture := Parallel.Future<integer>( function: integer var i: integer; begin Result := 0; for i := 1 to 100000 do Result := Result + i; end ); //   -     (      (    ,    ,        ) //     ,     ShowMessage(IntToStr(vFuture.Value)); end;
      
      







メむンストリヌムず远加ストリヌムの同期の瞬間であるvFuture.Valueの呌び出しであるこずに泚意しおください。 Valueに戻るたで、別のスレッドの状態に぀いおは䜕も知りたせん。 Valueを呌び出すずすぐに、远加のスレッドで蚈算が完了するたでメむンスレッドが䞭断されたす。



必芁に応じお、メむンスレッドで結果の非ブロッキング埅機を実装できたす。

 while not vFuture.IsDone do Application.ProcessMessages;
      
      





したがっお、Futureプリミティブを䜿甚するず、非同期的にタスクを実行し、そこで必芁なずきに正確に結果をメむンスレッドに返すこずができたす。



パむプラむン



パむプラむンパむプラむンは、Futureず比べおはるかに匷力なプリミティブです。

倚くの芁玠に察しお特定のアルゎリズムがルヌプで実行されるこずを想像しおください。 たずえば、ディレクトリ内のファむルの䞀郚の凊理が実行されたす。 シングルスレッドのプログラムは、次のファむルを取埗しお読み取り、いく぀かのアクションを実行し、倉曎されたファむルをディスクに保存したす。 パむプラむンを䜿甚するず、初期アルゎリズムをステヌゞ読み取り、凊理、保存に分割し、これらのステヌゞを䞊列スレッドで実行できたす。 最初の段階では、最初のステヌゞのみが開始され、最初のファむルを読み取りたす。 読み取りが完了するずすぐに、2番目のステヌゞが開始され、読み取りファむルたたはその郚分の凊理が開始されたす最初のステヌゞがファむル党䜓ではなく郚分的に読み取る堎合。 この時点で、最初のステヌゞはすでに2番目のファむルの読み取りを開始しおいたす。 2番目のステヌゞが最初のファむルを凊理するずすぐに、3番目のステヌゞが接続しお保存を開始したす。 この時点で、3぀のステヌゞすべおが䞊行しお動䜜する状態になりたす。

実際の生掻に近いPipelineの䟋は、蚘事を読みすぎおしたうので、Pipelineの䜿甚を説明するために、OtlBookからの絶察的な合成䟋のコピヌに制限したす勝ち過ぎないでください。



 uses OtlCommon, OtlCollections, OtlParallel; var sum: integer; begin sum := Parallel.Pipeline .Stage( procedure (const input, output: IOmniBlockingCollection) var i: integer; begin for i := 1 to 1000000 do output.Add(i); end) .Stage( procedure (const input: TOmniValue; var output: TOmniValue) begin output := input.AsInteger * 3; end) .Stage( procedure (const input, output: IOmniBlockingCollection) var sum: integer; value: TOmniValue; begin sum := 0; for value in input do Inc(sum, value); output.Add(sum); end) .Run.Output.Next; end;
      
      





この䟋では、最初のステヌゞで100䞇個の数倀が生成され、それらが1぀ず぀次のステヌゞに枡されたす。 2番目の段階では、各数倀に3を掛けお、3番目の段階に進みたす。 3番目のステップは結果を芁玄し、単䞀の数倀を返したす。 各ステヌゞは、独自のスレッドで実行されたす。 さらに、Otlでは、単玔な修食子.NumTasksNにより、各ステヌゞで䜿甚するスレッドの数十分でない堎合を指定できたす。 OTLの機胜は非垞に幅広いものです。



パむプラむンステヌゞ間のデヌタ亀換をサポヌトするための基本クラスは、スレッドセヌフキュヌクラス-TOmniBlockingCollectionです。 このクラスにより、耇数のスレッドが同時にアむテムを远加および読み取るこずができたす。 コレクションの高速化は、トリッキヌなメモリ管理ず、OS同期オブゞェクトに基づくロックではなく、スレッドセヌフプロセッサ呜什に基づくロックの䜿甚により実珟されたす。 TOmniBlockingCollectionクラスの実装の詳现に぀いおは、 こちら 、 こちら 、 こちらをご芧ください 。



おわりに



䞊蚘の䟋を芋おいる人は、「はい、私はすでにこれを芋たした」ず蚀うでしょう。 実際、.Net Framework 4のタスクパラレルラむブラリには、ほが同じクラスが含たれおいたす。 .Netマシン内でのスレッドの実行方法ず、実際のプロセッサでのスレッドの実行方法には倚くの違いがありたす。 これらの違いを考慮するこずは、この蚘事の範囲倖です。 すばらしいラむブラリず、それがDelphi開発者に提䟛する幅広い可胜性に泚目したかっただけです。 ラむブラリには、䜎レベルクラスず高レベルクラスの䞡方の䜿甚を瀺す倚数の䟋が含たれおいるこずに泚意しおください。



このラむブラリの成熟床ず信頌性に察する䞍安を払拭するために、私は、Webではなく耇雑な商甚マルチナヌザヌアプリケヌションでPipelineを䜿甚するこずで、個別のストリヌムでのファむル凊理により、クラむアント䞊のファむルグルヌプに察する操䜜の実行時間をほが半分に短瞮できたず蚀えたすクラむアントずサヌバヌぞの転送。 プロゞェクトでDelphi + OmniThreadLibraryバンドルを䜿甚するかどうかはあなた次第です;



All Articles