Swift 3の同時実行性。GCDおよびディスパッチキュー

iOSのマルチスレッド (同時実行)は、iOSアプリケーション開発者へのインタビュー中に尋ねられる質問、およびiOSアプリケーションを開発する際にプログラマーが犯す最大の間違いに常に含まれていると言わなければなりません。 したがって、このツールを完全にマスターすることが非常に重要です。

したがって、アプリケーションがあり、 main thread



で実行されます。 main thread



は、ユーザーインターフェイス( UI



)を表示するコードを実行します。 ネットワークからデータをダウンロードしたり、 main thread



(メインスレッド)で画像を処理するなど、アプリケーションに「時間のかかる」コードを追加し始めるとすぐに、 UI



の動作が大幅に遅くなり、完全な「フリーズ」に至ることさえあります。











このような問題が発生しないように、アプリケーションのアーキテクチャを変更するにはどうすればよいですか? この場合、マルチスレッド( oncurrency



)が助けとなり、2つ以上の独立したタスク( tasks



)を同時に実行できます:計算、ネットワークまたはディスクからのデータのダウンロード、画像処理など。



任意の時点でプロセッサがタスクの1つを実行でき、対応するスレッドがそれに割り当てられます。

シングルコアプロセッサ(iPhoneおよびiPad)の場合、タスクが実行される「スレッド」間の複数の短期スイッチによってマルチスレッド( oncurrency



)が実現され、シングルコアプロセッサでのタスクの同時実行の信頼性の高いアイデアが作成されます。 マルチコアプロセッサ(Mac)では、タスクに関連付けられた各「スレッド」に、タスクを実行するための独自のカーネルが提供されるという事実によって、マルチスレッドが実現されます。 これらの技術はどちらも、マルチスレッド( oncurrency



)の一般的な概念を使用しています。



アプリケーションにマルチスレッドを導入することに対する一種の支払いは、さまざまなスレッドで安全なコード実行を確保することが難しいことです( thread safety



)。 タスク( tasks



)を並行して動作させるとすぐに、異なるタスク( tasks



)が同じリソースにアクセスしたい、たとえば異なるスレッドの同じ変数を変更したい、または取得したいという事実に関連する問題があります他のタスクによってすでにブロックされているリソースへのアクセス。 これにより、他のスレッドのタスクによって使用されているリソース自体が破壊される可能性があります。



iOSプログラミングでは、 スレッドGrand Central Dispatch (略してGCD)、および操作という複数のツールの形式で開発者にマルチスレッドが提供され、ユーザーインターフェイスの生産性と応答性を向上させるために使用されます。 これは低レベルのメカニズムであるため、 Thread



は考慮しませんが、この記事ではGCD



に焦点を当て、将来の出版物ではOperation



GCD



上に構築されたオブジェクト指向API



)に焦点​​を当てAPI







Swift 3



が登場する前は、 Grand Central Dispatch (GCD



)などの強力なフレームワークにはCベースのAPI



がありましたが、これは一見魔法のように思えます。タスク。

Swift 3



すべてが劇的に変化しました。 GCD



は、非常に使いやすい新しい完全にSwift



構文を受け取りました。 古いAPI GCD



に少しでも精通していれば、まったく新しい構文は簡単に理解できます。 そうでない場合は、別の通常のiOS



プログラミングセクションを勉強する必要があります。 新しいGCD



フレームワークは、すべてのiOS



デバイスを含むApple Watch



からApple TV



およびMac



まで、すべてのApple



デバイスのSwift 3



で動作します。



もう1つの良いニュースは、 Xcode 8



以降では、 Playgroud



などの強力で視覚的なツールを使用してGCD



Operation



を学習できることです。 Xcode 8



PlaygroudPage



と呼ばれる新しいヘルパークラスを導入します。これは、 Playgroud



を無期限にPlaygroud



させる機能を備えています。 この場合、 DispatchQueue



キューは、ジョブが完了するまで実行できます。 これは、ネットワーク要求にとって特に重要です。 PlaygroudPage



クラスを使用するには、 PlaygroudSupport



モジュールをインポートする必要があります。 このモジュールを使用すると、 run loop



にアクセスし、ライブUI



表示し、 Playgroud



非同期操作を実行することもできます。 以下に、この設定が実際にどのように見えるかを示します。 Xcode 8



この新しいPlayground



機能により、 Swift 3



マルチスレッド化について非常に簡単かつ直感的に学習できます。



concurrency



よりよく理解するために、 Apple



は両方のツールがGCD



Operation



動作する抽象的な概念をいくつか導入しました。 基本的な概念はqueue



です。 したがって、 iOS



アプリケーションの開発者の観点からiOS



マルチスレッド化について話すときは、キューについて話します。 キューは、たとえば映画館へのチケットなど、人々が購入するために並んでいる通常のキューですが、この場合、クロージャーが並んでいます( closure



-コードの匿名ブロック)。 システムは単にキューに従ってそれらを実行し、次のキューを順番に「引き出し」て、このキューに対応するスレッドで実行するように開始します。 キューはFIFO



パターン( FIFO



従います。つまり、最初にキューに入れられた人が最初に実行されます。 多くのキューを持つことができ、システムは各キューの1つをクロージャーに「引き出し」、それらを開始して独自のスレッドで実行します。 これにより、マルチスレッド化が実現します。



ただし、これはiOS



oncurrency



がどのように機能するかの一般的な考え方にすぎません。 陰謀は、これらのキューが相互に関連するタスクを完了するという意味で(シーケンシャルまたはパラレル)、これらのタスクがキューに配置される機能(同期または非同期)であり、それによって現在のキューをブロックするかしないかです。



シリアル( serial



)およびパラレル( concurrent



)キュー



キューの先頭にあるタスク(クロージャ)がiOSによって「プル」され、完了するまで実行され、次のアイテムがキューからプルされる場合、キューは「 serial



」になります。 これは、 serial queue



またはシリアルキューです。 システムは、キューの先頭にあるクロージャーを「プル」し、特定のスレッドで実行を開始すると、キューを「c oncurrent



」(マルチスレッド)にできます。 システムにまだリソースがある場合、キューから次の要素を取得し、最初の関数がまだ機能している間に別のスレッドで実行するためにそれを開始します。 そして、システムは多くの機能を拡張できます。 マルチスレッドの一般的な概念と"concurrent queues"



(マルチスレッドキュー)を混同しないように、 "concurrent queue"



並列キューと呼び"concurrent queue"



。この並列処理の技術的な実装に入らずに、相互に関連するタスクの順序を参照します。











serial



(シーケンシャル)キューでは、クロージャーは実行された順序で厳密に完了しますが、 concurrent



(パラレル)キューでは、ジョブは予測不能な方法で終了します。 さらに、 serial



キュー上の特定のタスクグループの合計実行時間が、 concurrent



キュー上の同じタスクグループの実行時間を大幅に超えていることがわかります。 現在のserial



(シーケンシャル)キューでは、1つのタスクのみが実行され、 concurrent



(パラレル)キューでは、現在の時間のタスク数が変更できます。



同期および非同期のタスク実行



queue



作成されると、 async



現在のキューに関連する同期実行とasync



現在のキューに関連する非同期実行の2つの関数を使用してタスクをqueue



に配置できます。



同期sync



関数は、タスクが完了した後にのみ制御を現在のキューに返し、現在のキューをブロックします。











async



機能は、 sync



機能とは対照的に、別のキューでジョブを開始した直後に、完了するのを待たずに、現在のキューに制御を戻します。 したがって、 async



機能は、現在のキューでのタスクの実行をブロックしません。











非同期実行の場合、シーケンシャル( serial



)キューとしての「別のキュー」になることがあります。











および並列( concurrent



)キュー:











開発者のタスクは、 sync



機能を使用してsync



的にまたは非同期機能を使用してasync



的にキューを選択し、タスク(通常はクロージャー)をこのキューに追加することのみです。その後、 iOS



のみが機能します。



この投稿の最初に示したタスクに戻り、「ネットワークからのデータ」ネットワークから別のキューにデータを受信するタスクの実行を切り替えます。











別のDispatch Queue



ネットワークからData



を受信した後、 Main thread



送り返しMain thread















別のDispatchQueue



キューでネットワークからData



を受信すると、 Main thread



は解放され、 UI



で発生するすべてのイベントを処理します。 この場合の実際のコードがどのように見えるか見てみましょう:







かなりの時間がかかり、 Main queue



をブロックする可能性があるimageURL imageURL



を介してデータをダウンロードするには、このリソース集中型タスクを、 qos



.utility



等しいグローバル並列キューに.utility



(詳細は後述)。



  let imageURL: URL = URL(string: "http://www.planetware.com/photos-large/F/france-paris-eiffel-tower.jpg")! let queue = DispatchQueue.global(qos: .utility) queue.async{ if let data = try? Data(contentsOf: imageURL){ DispatchQueue.main.async { image.image = UIImage(data: data) print("Show image data") } print("Did download image data") } }
      
      







データdata



を受信した後data



再びMain queue



に戻り、このデータをimage1.image



してUI



要素image1.image



を更新します。

Main queue



から「コストのかかる」タスクの実行を「迂回」して、再びそれに戻るために、別のキューへの切り替えのチェーンを実行することがいかに簡単であるかがわかります。 コードはGithubのEnvironmentPlayground.playgroundにあります。



Main queue



から別のスレッドへの高価なジョブの切り替えは常に非同期であることに注意してください。

「現在のスレッド」は、ジョブが別のキューで終了するのを待機するように強制されるため、キューのsync



方法には非常に注意する必要があります。 Main queue



sync



メソッドを呼び出さないでください。アプリケーションのdeadlock



つながるため
です。 (これについては以下で詳しく説明します)



グローバルキュー



特別に作成する必要があるユーザーキューに加えて、 iOS



システムは、 out-of-the-box



グローバルキューを開発者に提供します。 彼らの5:



1.)シーケンシャルキューMain queue



。ユーザーインターフェイス( UI



)を使用したすべての操作が行われます。

 let main = DispatchQueue.main
      
      





ユーザーインターフェイス( UI



)、 UIButton



またはUI--



UIButton



何かを行う関数またはクロージャーを実行する場合は、その関数またはクロージャーをMain queue



配置する必要がありMain queue



。 このキューは、グローバルキューの中で最も高い優先度を持っています。



2.) qos



品質が異なり、優先度が異なる4つのバックグラウンドconcurrent



(並列)グローバルキュー:

 //   let userInteractiveQueue = DispatchQueue.global(qos: .userInteractive) let userInitiatedQueue = DispatchQueue.global(qos: .userInitiated) let utilityQueue = DispatchQueue.global(qos: .utility) //    let backgroundQueue = DispatchQueue.global(.background) //   let defaultQueue = DispatchQueue.global()
      
      





これらの各キューApple



Apple



抽象的な「サービス品質」 qos



Quality of Service



略)を授与されApple



、タスクに対して何をすべきかを決定する必要があります。



さまざまなqos



qos



、それぞれの目的を説明しqos









また、デフォルトの.default



concurrency



キュー.default



があります。これは、 qos



「サービス品質」 qos



不足を報告しqos



。 演算子を使用して作成されます:

 DispatchQueue.global()
      
      





他のソースからqos



情報を判別できる場合はそれが使用され、そうでない場合は.userInitiated



.utility



間でqos



使用されます。



これらのグローバルキューはすべてSYSTEMグローバルキューであり、このキュー内のタスクは私たちのタスクだけではないことを理解することが重要です。 また、1つを除くすべてのグローバルキューがconcurrent



(並列)キューであることを知ることも重要です。



ユーザーインターフェイス用の特別なグローバルシリアルキュー-メインキュー



Appleは、唯一のグローバルserial



(シーケンシャル)キューを提供しています-これは、上記のMain queue



です。 このキューでは、 UI



変更に関連しないリソース集約的な操作(たとえば、ネットワークからのデータのダウンロード)を実行して、この操作の間UI



を「フリーズ」せず、ユーザーインターフェイスをいつでもユーザーの操作に応答させないようにすることは望ましくありません。ジェスチャーで。











このようなリソース集中型の操作を他のスレッドまたはキューに「流用」することを強くお勧めします。











もう1つ厳しい要件があります。 Main queue



のみ、 UI



要素を変更できます。



これは、 Main queue



UI



アクションに「応答する」だけでなく(はい、これが主な理由)、ユーザーインターフェースをマルチスレッド環境での「混乱」から保護する、つまり、ユーザーアクションは、厳密に順番に実行されます。 UI



要素が異なるキューでアクションを実行できるようにすると、描画が異なる速度で発生し、アクションが交差し、画面上で完全に予測不能になる可能性があります。 Main queue



を一種の「同期ポイント」として使用し、画面に「描画」したいすべてのユーザーが戻ります。



マルチスレッドの問題



タスクの並列動作を許可するとすぐに、異なるタスクが同じリソースにアクセスしたいという事実に関連する問題があります。

主に3つの問題があります。







競合状態



private



キューでvalue



変数を非同期に変更し、現在のスレッドでvalue



を表示すると、 race condition



最も単純なケースを再現できます。







通常の変数value



とそれを変更する通常の関数changeValue



があり、意図的にsleep(1)



演算子を使用して、変数の変更にかなりの時間がかかるようにしました。 async



を使用してchangeValue



関数をchangeValue



実行すると、変更されたvalue



変数に配置する前に、現在のスレッドでvalue



変数を別の値にリセットできます。これがrace condition



です。 このコードは、次の形式での印刷に対応しています。







race condition



」と呼ばれる現象を明確に示す図:







async



メソッドをsync



置き換えましょう:







印刷と結果の両方が変更されました:



<img



そして、「 race condition



」と呼ばれる現象がない図:







キューのsync



メソッドには非常に注意する必要がありますが、「現在のスレッド」はジョブが他のキューで終了するまで待機するため、 sync



メソッドは競合状態を避けるために非常に便利です。 race condition



現象をシミュレートするコードは、GithubのfirstPlayground.playgroundで表示できます。 後で、異なるストリームで受信した文字列を形成するときの実際の「 race condition



」を示します。 「バリア」を使用してストリングを形成するエレガントな方法も提案されます。これにより、「 race conditions



」を回避し、生成されたストリングをスレッドセーフにすることができます。



優先順位の逆転







の概念は、リソースのロックに密接に関連しています。







優先度が低(A)と高(B)の2つのタスクがシステムにあるとします。時間T1で、タスク(A)はリソースをブロックし、リソースの提供を開始します。時間T2で、タスク(B)は優先度の低いタスク(A)を押し出し、時間T3でリソースを引き継ぎます。ただし、リソースがロックされているため、タスク(B)は保留され、タスク(A)は実行を継続します。時間T4で、タスク(A)はリソースのサービスを完了し、ロックを解除します。タスク(B)はリソースを予期しているため、すぐに実行を開始します。

タイムスパン(T4-T3)は、限定優先順位反転と呼ばれます。この期間には、計画ルールと論理的な矛盾があります。優先度の低いタスクが実行されている間、優先度の高いタスクが待機しています。



しかし、これは最悪ではありません。システムで3つのタスクが機能するとします:低優先度(A)、中優先度(B)、高優先度(C):







リソースがタスク(A)によってブロックされ、タスク(C)によって必要とされる場合、同じ状況が観察されます-高優先度タスクがブロックされますしかし、タスク(B)がリソースを待機する(C)後(A)に取って代わったとします。タスク(B)は競合について何も知らないので、一定期間(T5-T4)にわたって好きなだけ実行できます。さらに、(B)に加えて、システムには他のタスクがあり、優先度は(A)よりも大きく(B)低くなります。したがって、期間(T6-T3)の期間は一般に無期限です。この状況は、無制限の優先順位反転と呼ばれます。



一般に、優先度の制限の反転は避けることはできませんが、マルチスレッドアプリケーションにとって無制限のものほど危険ではありません。優先度の低いすべての「干渉」タスクの優先度を強制的に上げることで排除されます。



以下に、DispatchWorkItem



オブジェクトを使用して現在のキュー内の個々のタスクの優先度を上げる方法を示します。



デッドロック



デッドロックは、リソースロックがネストされているときに発生する可能性があるシステムの緊急状態です。システムに2つのリソース(XおよびY)を使用する低(A)および高(B)優先度の2つの







タスクがあるとします。時間T1で、タスク(A)はリソースXをブロックします。時刻T3でリソースYをブロックする優先タスク(B)。タスク(B)がリソースYを解放せずにリソースX(T4)をブロックしようとすると、保留され、タスク(A)が続行されます。時間T5で、タスク(A)がXを解放せずにリソースYをブロックしようとすると、相互ロックの状態が発生します。タスク(A)と(B)のいずれも制御を取得できません。



相互ロックは、システムがリソースへの依存(ネスト)マルチスレッドアクセスを使用する場合にのみ可能です。ネストが使用されていない場合、またはリソースが優先度増加プロトコルを使用している場合、相互ブロッキングを回避できます。

投稿の冒頭にあるタスクで、バックグラウンドキューでネットワークからデータを受信した後、メインキューに戻るためにsyncメソッドを使用しようとすると、デッドロック(deadock



)が発生します。アプリケーションでデッドロック(が発生するため



、でメソッドsync



呼び出さmain queue



ないでdeadlock



ください!



実験環境



実験のために、我々は、使用するPlayground



モジュールC無限の時間に合わせて調整PlaygroundSupport



し、クラスPlaygroudPage



我々はキューに置かれたすべてのタスクを完了し、へのアクセスを得ることができたことmain queue



Playground



cコマンドで何らかのイベントを待つのを止めることができPlaygroundPage.current.finishExecution()



ます。

別のクールな機能がありますPlayground



- UI



チームの助けを借りて「生きている」対話する機能

 PlaygroundPage.liveView = viewController
      
      





およびアシスタントエディター(Assistant Editor



)。たとえば、を作成する場合、viewController



を確認するには、コードを無制限に実行viewController



するように構成Playground



し、アシスタントエディターを有効にするだけですAssistant Editor



)。コマンドをコメント化して、手動でPlaygroundPage.current.finishExecution()



停止するPlayground



必要があります。







Playground



実験環境テンプレートのcコードは、EnvironmentPlayground.playgroundという名前で、Githubにあります。



1.最初の実験。グローバルキューとジョブ



簡単な実験から始めましょう。一貫性のある1:我々はまた、世界的なキューの数を定義するmainQueue



ことがある- main queue



そして4つの並列(concurrent



queues



- 、userInteractiveQueue



デフォルトで設定できます- ジョブとして、10個の同一の文字と現在のキューの優先度を印刷することを選択します。1文字を印刷する別のタスク、高い優先度で実行します。userQueue



utilityQueue



backgroundQueue



concurrent queue



defautQueue











task



taskHIGH











2.第二の実験は、に適用されます同期および非同期的にグローバルキューに



たとえば、グローバルキューを受け取ったら、methodを使用してSYNCHRONOUSLYに、またはmethodを使用してASYNCHRONOUSLYにuserQueue



タスクを実行できます同期実行の場合、すべてのタスクが次々に順番に開始され、次のタスクが前のタスクの完了を明確に待っていることがわかります。さらに、最適化として、可能であれば、sync関数は現在のスレッドでクローズをトリガーでき、グローバルキューの優先順位は重要ではありません。それは私たちが見るものです。非同期実行の場合、タスクの完了とグローバルキューの優先順位を待たずにタスクが開始することがわかります。sync



async











sync







async











userQueue



での優先度の高いコードの実行Playground



その結果、タスクuserQueue



が頻繁に実行されることはありません。



3. 3番目の実験。プライベートシリアルキュー



グローバルキューに加えPrivate



て、クラスイニシャライザーを使用してカスタムキューを作成できますDispatchQueue



カスタムキューを







作成するときに指定する必要がlabel



あるのはApple



、逆DNS



表記(“com.bestkora.mySerial”



として指定することをお勧めする一意のラベルです。このキューはデバッガーでこの名前で表示されます。ただし、これはオプションであり、一意である限り任意の文字列を使用できます。キューのlabel



初期化時以外他の引数を指定しない場合Private



、デフォルトで順次(.serial



)キューが作成されます。キューを初期化するときに設定できる他の引数がありますが、それらについては少し後で説明します。

ユーザーどのように見てPrivate



、一貫した場所mySerialQueue



使用sync



してasync



:メソッド







の場合は、同期 sync



の最適化機能としてあるため、私たちは型キュー実験3と同様の状況を見るには、重要ではありませんsync



、電流の流れの閉鎖をトリガすることができます。それは私たちが見るものです。メソッド



を使用しasync



、シーケンシャルキューが現在のキューに対して非同期にmySerialQueue



タスクを実行できるように



するとどうなりますか?この場合、プログラムは停止せず、このタスクがキューで完了するまで待機しませんmySerialQueue



管理はすぐにタスクを完了します



タスクと同時にそれらを実行します





4. QoS



レッツは、私たちの割り当てPrivate



シリアル回線serialPriorityQueue



、サービス品質QoSを.userInitiatedと等しく、非同期キューの最初の仕事に入れ



、その後、



この実験は、私たちの新しい場所があることを納得させるでしょうserialPriorityQueue



、本当に一貫している、との使用にもかかわらずasync



方法、タスクは次々に実行されます到着順に:







したがって、マルチスレッドコードを実行するには、methodを使用するだけでは不十分async



です。キューが異なるか、キュー自体が私は平行です(.concurrent



)。並列(.concurrent



)キューを使用した実験5では、プライベート並列(.concurrent



)キューを使用した同様の実験が表示されますworkerQueue



、しかし、同じタスクをこのキューに入れると、まったく異なる状況になります。優先順位の異なる



順次Private



キューを使用して



最初にこのキューにタスクを非同期的に配置し、次にタスク





キューserialPriorityQueue1



c qos .userInitiated





キューserialPriorityQueue2



c タスクのqos .background











マルチスレッド実行があり、タスクはよりserialPriorityQueue1



高いサービス品質のキュー実行されることが多くなりますqos: .userIniatated



たとえば、関数使用して、サービスの品質を変更することにより、



任意のキューでのタスクの実行をDispatchQueue



所定の時間遅延させることができますnow() + 0.1



asyncAfter



qos











5. 5番目の実験は、プライベート並列(同時)キューに関するものです。



Private



parallel(.concurrent



)キューを初期化するには、Private queueを初期化するときにattributes



等しい引数の値を示すだけで十分.concurrent



です。この引数を指定しない場合、Private



キューは順次(.serial



)になります。引数qos



も必須ではなく、問題なくスキップできます。同等のサービス品質を



並列キュー割り当て、タスクを最初にこのキューに非同期的に配置してから、新しい並列キューは実際に並列であり、その中のタスクは同時に実行されますが、4番目の実験(1つの順次回すworkerQueue



qos



.userInitiated











workerQueue



serialPriorityQueue



)、彼らは引数をattributes



等しく設定します.concurrent











絵は1つの連続したキューと比較して完全に異なります。その場合はすべてのタスクは、厳密にそれらが実行するために来た順序で行われているされている私たちの並列(マルチスレッド)キューworkerQueue



複数のスレッドに「分割」、タスクが実際に並行して行うことができますいくつかのクエストを記号で



、後にキューに入れられたとして、workerQueue



並列スレッドでは高速です。



レッツ・使用並列Private



異なる優先順位を持つキュー:



すべてのworkerQueue1



C qos .userInitiated





すべてのworkerQueue2



C qos .background











異なる配列を有すると同じパターンPrivate



2回目の実験でバーストします。workerQueue1



優先度の高いqueueでタスクが実行される頻度が高いことがわかります。



引数を使用して遅延実行でキューを作成attributes



し、メソッドを使用して適切なタイミングでそのタスクの実行をアクティブ化できますactivate()











6. 6番目の実験では、DispatchWorkItemオブジェクトを使用します。



Dispatch



キュー内のさまざまなタスクの実行を管理するための追加機能が必要なDispatchWorkItem



場合は、サービス品質を設定できるタスクを作成できますqos











これにより、パフォーマンスに影響します。[.enforceQoS]



準備中にフラグを設定することによりDispatchWorkItem



、そのタスクのhighPriorityItem



他のタスクよりもタスクの優先度が高くなります同じキュー:







これにより、Dispatch Queue



特定のサービス品質に対して特定のタスクの優先度を強制的に上げることができるため、qos



「優先度の逆転」の現象に対処できます。 2つのタスクhighPriorityItem



が最新のタスクから開始されるという事実にもかかわらず、フラグ[.enforceQoS]



と優先順位を高くすることにより、最初から実行されていることがわかります。.userInteractive



。さらに、タスクhighPriorityItem



は異なるキューで複数回実行できます。



我々はフラグを削除した場合[.enforceQoS]











ジョブはhighPriorityItem



、サービスの品質になりますqos



彼らが実行するオンに設定されている。







しかし、まだ彼らは非常に各キューの始まりに陥ります。これらすべての実験のコードは、GithubのfirstPlayground.playgroundにあります。



クラスでDispatchWorkItem



プロパティがあるisCancelled



とメソッドの数を:







メソッドの存在にもかかわらずcancel()



のためDispatchWorkItem



GCD



、まだ、すでに特定のライン上で開始された回路を、削除しません。現在できることはDispatchWorkItem



、メソッドを使用して「リモート」としてマークすることcancel()



です。メソッド呼び出しの場合cancel()



前に発生したDispatchWorkItem



方法によってキューに入れられますasync



、それはDispatchWorkItem



実行されません。特定のキューで開始されたクロージャーを削除する方法がわからないという事実Operation



だけでなく、メカニズムを使用する必要がある場合がある理由の1つですクラスとそのメソッドおよびクラスインスタンスメソッドを使用できます。GCD



GCD







DispatchWorkItem



notify (queue:, execute:)



DispatchQueue







 async(execute workItem: DispatchWorkItem)
      
      





ポストの最初に提示された問題を解決するために-ネットワークからイメージをダウンロードします:特定のアドレスで「ネットワーク」からデータを取得することで構成されるクラスの







インスタンスの形式で同期タスクを形成します。関数を使用して、サービス品質で並列グローバルキュー非同期ジョブ実行するworkItem



DispatchWorlItem



data



imageURL



workItem



queue



qos: .utility





 queue.async(execute: workItem)
      
      







機能を使用する

 workItem.notify(queue: DispatchQueue.main) { if let imageData = data { eiffelImage.image = UIImage(data: imageData)} }
      
      





データアップロードの終了に関する通知を待っていますdata



これが起こったら、私たちは、画像要素を更新UI



eiffelImage











コードが上に配置されているのGithub上LoadImage.playground



パターン1.ネットワークから画像をダウンロードするためのコードオプション



2つの同期タスク

があります。ネットワークからデータを受信する

 let data = try? Data(contentsOf: imageURL)
      
      





data



ユーザーインターフェイスデータに基づく更新UI





 eiffelImage.image = UIImage(data: data)
      
      





これはGCD



、コンポーネントUIKit



がメインスレッドから排他的に機能するため、バックグラウンドスレッドで作業を行い、結果を表示するためにメインスレッドに返す必要がある場合に、マルチスレッドメカニズムを使用して実行される典型的なパターンです。



これは、従来の方法で行うことができます。







既製の非同期APIを使用するURLSession











またはusingを使用しDispatchWorlItem



ます。







最後に、同期タスクを常に非同期「シェル」に「ラップ」して実行できます。







このパターンのコードはLoadImage.playgroundにありますgithubで



パターン2。機能は、GCDを使用してテーブルビューおよびコレクションビューのネットワークから画像をダウンロードします。



一つだけで構成されて非常に簡単なアプリケーションの例を考えてみましょうImage Table View Controller



細胞のみがインターネットからダウンロードされたイメージとブートプロセスを示す活動の指標が含まれているテーブル、:







ここのクラスであるImageTableViewController



スクリーンのサービスフラグメントImage Table View Controller











およびクラスImageTableViewCell



ロードされているテーブルセルについては、 image:







イメージは通常の古典的な方法でロードされます。クラスのモデルImageTableViewController



は8の配列ですURLs







  1. エッフェル塔
  2. ヴェネツィア
  3. スコットランドの城
  4. Cassiniサテライト-他のネットワークよりもはるかに長いネットワークからの読み込み
  5. エッフェル塔
  6. ヴェネツィア
  7. スコットランドの城
  8. 北極圏


アプリケーションを起動し、8つの画像すべてを表示するのに十分な速さでスクロールを開始すると、画面を離れるまでCassini Satelliteが起動しないことがわかります。明らかに、他のすべてのものよりもロードにかなり時間がかかります。







しかし、最後までスクロールして最後のセルで「北極」を見た後、非常に短い時間の後、突然カッシーニ衛星に置き換えられることがわかりました







これは、このような単純なアプリケーションの誤った機能です。問題は何ですか?実際には、テーブルのセルはメソッドのおかげで再利用可能dequeueReusableCell



です。セル(新規または再利用)が画面にヒットするたびに、ネットワークから非同期に画像がダウンロードされ(この時点で「ホイール」が回転します)、ダウンロードが完了して画像が受信されるとすぐに、UI



このセルが更新されます。しかし、画像がロードされるのを待たずに、テーブルをスクロールし続けて、セル(「Cassini」)がそれ自体を更新せずに画面を離れますUI



。ただし、新しい画像が下に表示され、画面を出た同じセルが再利用されますが、別の画像(「北極」)にはすぐにロードおよび更新されますUI



。この時点で、このセルで以前に起動されたCassiniのダウンロードが返され、画面が更新されますが、これは間違っているためです。彼らはさまざまな時に戻ってきます:







どうすれば状況を修正できますか?メカニズムのフレームワーク内で、GCD



画面を離れたセルの画像の読み込みをキャンセルすることはできませんがimageData



、ネットワークから来たときにURL



、このデータの読み込みの原因url



となったものを確認し、ユーザーが現時点でこのセルに入れたいものと比較できますimageURL











これですべてが正常に機能します。したがって、マルチスレッドプログラミングには非標準の想像力が必要です。実際、マルチスレッドプログラミングのいくつかのことは、コードが記述された順序とは異なる順序で行われます。付録GCDTableViewControllerは上にあるのGithub



パターン3. DispatchGroupsの使用



非同期に実行する必要があるタスクがいくつかあり、それらが完了するのを待つ場合、DispatchGroup



作成が非常に簡単なグループが適用されます。



 let imageGroup = DispatchGroup()
      
      







我々は、「ネットワークから」4つの異なる画像をダウンロードする必要があるとしましょう:







メソッドは、queue.async(group: imageGroup)



グループに任意の割り当てを追加することができます(同期)任意の行で実行されているqueue











私たちは、グループを作成imageGroup



し、法によって、このグループに配置されたasync (group: imageGroup)



グローバルなパラレルターンへの非同期ロードの画像のための2つの割り当てDispatchQueue.global()



そしてDispatchQueue.global(qos:.userInitiated)



、サービス品質でグローバル並列キューにイメージを非同期的にロードする2つのタスク.userInitiated



。異なるキューで実行されているタスクを同じグループに追加できることが重要です。グループ内のすべてのタスクが完了すると、関数notify



呼び出されます-これはグループ全体の一種のコールバックブロックであり、すべての画像を同時に画面に配置します。







グループにはスレッドセーフな内部カウンターが含まれており、メソッドを使用してタスクがグループに追加されると自動的に増加しますasync (group: imageGroup)



。タスクが実行されると、カウンターが1つ減り、すべての長期操作が完了した後にコールバックブロックが呼び出されることが保証されます。同期操作のグループの形成に関する実験は、GithubのPlayground GroupSyncTasks.playgroundで提示されます。



グループに同期操作だけでなく非同期操作もある場合は、スレッドセーフカウンターを手動で制御できますenter()



メソッドはカウンターを増やし、メソッドはleave()



減らします。GithubのPlayground GroupAsyncTasks.playgroundを使用して、グループ内の非同期操作の配置を学習します非同期タスクをグループに配置し、画面の上部に表示します。







比較のために、画面の下部に、通常の方法で取得した同じ画像をグループにまとめずに次々に配置します。あなたはすぐに先頭に同じ画像の外観の違いを感じるだろうし、下部にある:初めにメソッド呼び出しが、次々と、画面下部の画像、および画面のすべて一度画像トップがあるでしょうasyncGroup ()



し、asyncUsual ()



逆の順序で:







打込みを混合操作のグループ:同期および非同期:







結果は同じになります。



パターン4.スレッドセーフ変数。分離キュー



のは、バーストで私たちの最初の実験に戻って行こうGCD



Swift 3



:と行のタスクの時系列(垂直方向)のシーケンスを維持し、それによって、水平な方法で異なるライン上のタスクの結果をユーザに提示しようと







、私は通常どおりの結果格納するために使用されることを言っているNEpotochno-をセキュアSwift 3



、文字列usualString: String



、およびスレッドセーフ(スレッドセーフ)文字列safeString: ThreadSafeString







 var safeString = ThreadSafeString("") var usualString = ""
      
      







このセクションの目的は、スレッドセーフな文字列をでどのように配置するかを示すSwift 3



ことです。これについては後で詳しく説明します。



スレッドセーフ文字列を使用したすべての実験は、GithubのPlayground GCDPlayground.playgroundで行われます。



Iが蓄積両方の行の情報を順にビット割り当てを変更しますusualString



safeString











Swift



キーワードで宣言された任意の変数がlet



一定であるため、スレッドセーフ(thread-safe



)。キーワードvar



を使用して変数を宣言すると、変数は可変(mutable



)および非スレッドセーフ(thread-safe



)特別に設計されるまで。2つのスレッドが同じメモリブロックを同時に変更し始めると、このメモリブロックが破損する可能性があります。さらに、あるスレッドで変数の値が別のスレッドで更新されているときに変数を読み取ると、「古い値」、つまり競合状態(race condition



が読み取られる危険があります。



スレッドセーフの理想的なオプションは、次の場合です。





幸いなことに、GCD



バリア(barrier



)と分離キューを使用して解決するエレガントな方法を提供します。











バリアGCD



は1つの興味深いことを行います。キューが完全に空になる瞬間を待ってから、クローズを実行します。バリアーがクロージャーの完了を開始するとすぐに、キューがこの時間中に他のクロージャーを実行しないようにし、基本的に同期関数として機能します。バリアによるクロージャが終了するとすぐに、キューは通常の動作に戻り、読み取りまたは別の書き込みと同時に記録が実行されないことが保証されます。



のは、それがスレッドセーフなクラスをどのように見えるかを見てみましょうThreadSafeString











関数isolationQueue.sync



「読み」回路を送信するために{result = self.internalString}



、分離の私たちの場所をisolationQueue



して結果を返す前に、終了を待っていますresult



。その後、読み取り結果が得られます。同期呼び出しを行わない場合は、コールバックブロックを導入する必要があります。キューはisolationQueue



並列(.concurrent



)であるため、このような同期読み取りは一度に数回実行できます。



この関数isolationQueue.async (flags: .barrier)



は、「record」、「add」、または「initialize」のクローズを分離キューに送信しますisolationQueue



。この関数async



は、「レコード」、「追加」、または「初期化」のクローズが実際に行われる前に制御が返されることを意味します。バリア部分(flags: .barrier)



は、キュー内の各クロージャーの実行が完了するまでクロージャーが実行されないことを意味します。その他の障害は、バリアの後に配置され、バリアが完了した後に実行されます。

実験の結果DispatchQueues



、スレッドセーフな(提示thread-safe



)ラインsafeString: ThreadSafeString



と法線はusualString: String



、上にあるのGithub上の遊び場GCDPlayground.playground



これらの結果を見てみましょう。



1.関数sync



グローバル平行線でDispatchQueue.global(qos: .userInitiated)



に関してPlayground











従来の非ストリームセーフラインの結果usualString



MATCHのスレッドセーフな文字列に結果safeString







2.関数async



グローバル平行線でDispatchQueue.global(qos: .userInitiated)



に関してPlayground











従来の非ストリームラインセーフの結果usualString



DO NOT MATCHスレッドセーフな文字列で結果を取得しますsafeString







3.機能sync



Private



シリアルラインDispatchQueue (label: "com.bestkora.mySerial")



遊び場に関連して:







平野上の結果は、スレッドセーフではありませんラインusualString



MATCHの結果をスレッドセーフな文字列にsafeString







4.機能async



上のPrivate



シリアルラインDispatchQueue (label: "com.bestkora.mySerial")



に関してPlayground











従来の非ストリームラインセーフの結果usualString



DO NOT MATCHスレッドセーフな文字列に結果をsafeString







5. async



タスクの機能



そして



上のPrivate



シリアル回線DispatchQueue (label: "com.bestkora.mySerial", qos : .userInitiated)











結果は、スレッドセーフなライン上に、通常ではありませんusualString



MATCHのスレッドセーフな文字列の結果がsafeString







6. async



タスクの機能



そして



異なるのPrivate



連続したラインDispatchQueue (label: "com.bestkora.mySerial", qos : .userInitiated)



、およびDispatchQueue (label: "com.bestkora.mySerial", qos : .background)











従来の非ストリームライン金庫の結果usualString



DO NOT MATCHスレッドセーフな文字列の結果とsafeString







7. async



タスクの機能



そして



上のPrivate



平行線DispatchQueue (label: "com.bestkora.mySerial", qos : .userInitiated, attributes: .concurrent)











従来の非ストリームライン金庫の結果usualString



DO NOT MATCHスレッドセーフな文字列の結果とsafeString







8. async



タスクの機能



そして



andを使用した異なるPrivate



並列キューで通常のスレッドセーフラインでの結果、スレッドセーフラインでの結果と一致しない9.機能の優先順位でCの変更:従来の非ストリームライン金庫の結果DO NOT MATCHスレッドセーフな文字列の結果と10.機能の優先順位のC変更:普通スレッドセーフではない線上結果MATCHのスレッドセーフライン上の結果ジョブとしてqos : .userInitiated



qos : .background











usualString



safeString







asyncAfter (deadline: .now() + 0.0, qos: .userInteractive)











usualString



safeString







asyncAfter (deadline: .now() + 0.1, qos: .userInteractive)











usualString



safeString







そして



時間間隔で

マルチスレッドの割り当てがある場所



そして



異なるキューで発生するか、1つの並列(.concurrent



)キューで発生するusualString



と、通常の行とスレッドセーフな行の間で不一致が発生しますsafeString







スレッドセーフな文字列を使用してsafeString



、我々はいわば、キューのプロパティとの同期と非同期の機能を見ることができ、右側の「鳥瞰」は、対応するタスクの実行は、次のとおりです。







あなたが使用していない場合はPlayground



、アプリケーションを、そしてXcode 8



それを使用することが可能であるThread Sanitizer



決意のためにrace condition



Thread Sanitizer



アプリケーション実行の段階で動作します。スキーム(Scheme



)を編集







することで開始できます。この例では、競合状態の検出が表示されます。Tsanアプリケーションコードはオンですgithubの



結論マルチスレッドプログラミングの問題を解決



するGCD



ためのいくつかのユースケースを検討しましたSwift 3



。次の記事ではOperations



、マルチスレッドプログラミングの実際の使用に関する質問について説明しますSwift 3







PS現在GCD API



すべてのプラットフォームで利用可能で、マルチスレッドアプリケーションを作成する優れた方法を提供します。しかし、現在のバージョンにSwift 3



マルチスレッドを記述するための構文構造がありません。開発チームSwift



は、マルチスレッドをより集中的に取り上げ、バージョンSwift 5



(2018)のマルチスレッド構文の実際の変更を準備し、2017年春/夏に議論を開始し、マニフェスト



2017年秋まで。IBM のプログラミング言語日でクリス・ルトナーは、既存のマルチスレッドプログラミングGCD API



async



関数を使用する、「運命のピラミッド」につながり、コメントなしでどのデータ/状態を認識するのが非常に難しいと述べました」独自の「何Dispatch Queue



や、関連するタスクは、これらの行によって行わ:







改善マルチスレッドのための可能な分野の一つ-の使用である俳優のモデル俳優、モデルである。)各actor



-で、実際には、DispatchQueue



+ ステータスこの場所が動作し、+ オペレーションこの行で実行:







しかし、これは多くのオファーの1つにすぎません。いま、検討するactors



async/await



不可分性をメモリモデルその他の関連トピック。マルチスレッドは、クライアントとサーバーの両方で新しいアプローチへの「扉を開く」ため、非常に重要です。ここで



進化Swift



を見ることができます



この記事は、マルチスレッドプログラミングが大量にあるためiTunesホストされているStanford CS193p Winter 17コース(iOS 10およびSwift 3に基づく)を使用して、SwiftでiOSプログラミングを学習する人に役立つ場合があります。



参照資料



WWDC 2016. Concurrent Programming With GCD in Swift 3 (session 720)

WWDC 2016. Improving Existing Apps with Modern Best Practices (session 213)

WWDC 2015. Building Responsive and Efficient Apps with GCD.

Grand Central Dispatch (GCD) and Dispatch Queues in Swift 3

iOS Concurrency with GCD and Operations

The GCD Handbook

GCD

Modernize libdispatch for Swift 3 naming conventions

GCD

GCD – Beta

CONCURRENCY IN IOS

www.uraimo.com/2017/05/07/all-about-concurrency-in-swift-1-the-present

All about concurrency in Swift — Part 1: The Present



All Articles