💬

【翻訳】Concurrency in Swift (Grand Central Dispatch Part 2)

2023/07/18に公開

このパートでは、以下のトピックを取り上げます。

  1. ディスパッチ・グループとは
  2. ディスパッチ・グループの実装方法 待機と通知ストラテジーを使って
  3. ディスパッチ・バリアとは
  4. Dispatch Barrierを使ってリーダとライタの問題を解決する方法

前回の続きです。GCDの基本がわかっていれば、この続きは可能です。

ディスパッチ・グループ

  1. ディスパッチ・グループは、1つ以上のタスクの実行が終了するまでスレッドをブロックする方法です。
    この動作は、指定したタスクがすべて完了するまで処理を進められないような場所で使うことができます。

  2. 例えば、あるデータを計算するために複数のタスクをディスパッチした後、
    グループを使用してそれらのタスクを待機させ、タスクが終了したらその結果を処理するような場合です。

  3. DispatchGroupは作業の集約的な同期を可能にする

図1に示すように、2つのタスクを同時に実行し、ディスパッチ・グループのwait関数を使用して、
これらのタスクの両方が実行されるまでスレッドをブロックして終了を待ちます。
以下のコードでは、以下のステップを実行しています。

waitを使用したディスパッチグループ

  1. "com.company.app.queue "というラベルのカスタムキューを作成

  2. ディスパッチ・グループを作成

  3. group.enter()を使用して、ブロックがグループに入ったことを手動で示す。
    注意: この関数の呼び出しは、dispatch_group_leave()とバランスをとる必要があります。
    さもないとアプリケーションがクラッシュします。

  4. タスク1がqueue.asyncを通して実行され、即座に戻り、GCDがこのタスクを同時に実行し始めます。

  5. group.enter()を使って、ブロックがグループに入ったことを手動で示します。

  6. タスク2がqueue.asyncを通して実行され、即座に戻り、GCDがこのタスクの同時実行を開始します。

  7. group.wait()(上記のすべてのタスクが終了するまで、ここにあるスレッドをブロックする
    (メインスレッドでこの関数を使用しないように)メインスレッドでこのメソッドを呼び出したので、
    メインスレッドをブロックします。


図1

図2に示すように、メイン・キューの代わりに並行キューでwait()メソッドを使用しているため、
バックグラウンド・スレッドがブロックされています。


図2

notifyを使ったディスパッチ・グループ

非同期でキューにディスパッチし、waitを使って作業をブロックするのは、
前の例でメイン・スレッドをブロックしているのを見ればわかるように、良いことではありません。
幸い、もっと良い方法があります。
DispatchGroupは、グループのすべてのタスクが完了したときに通知してくれます。
図3に示すように、両方のタスクが完了すると、Dispatch Groupはnotifyメソッドを呼び出します。


図3

図3.1と図3.2に示すように、notifyを使ったが、タスク1の後に "#3 finished "と表示され、
タスク3は終了したが、タスク2を待たなかった。タスク2をグループに追加しなかったので、
このタスクの完了を待たなかったためです。

グループを使用すると、一連のタスクを集約し、グループ上の動作を同期させることができます。
複数のワークアイテムをグループにアタッチし、それらを同じキューまたは異なるキューで
非同期に実行するようにスケジュールします。すべてのワークアイテムの実行が終了すると、
グループは完了ハンドルを実行します。


図3.1


図3.2

図3.3に示すように、enter()とleave()を追加したため、
グループは3つのタスクがすべて終了するまで待機します。


図3.3

Dispatchグループはqueueをパラメータとして取り、どのqueueで完了ブロックを実行するかを決定します。
図3.4に示すように、Dispatch.main() を指定すると、
メインスレッドに "#3 finished" と表示されます。


図3.4

ディスパッチ・バリア

  1. ディスパッチバリアは、同時実行キューを扱う際にシリアル形式のボトルネックとして動作する関数群です。

  2. DispatchWorkItem(task )をディスパッチキューに投入する際、その特定の時間、
    指定されたキューで実行される唯一のアイテムであるべきことを示すフラグを設定することができます。
    これは、ディスパッチバリアタスクの前にキューに投入されたすべてのアイテムが、
    バリアタスクの実行を開始する前に完了しなければならないことを意味します。

図4に示すように、我々は以下のステップを実行しました。

  1. カスタム並行ディスパッチ・キューを作成

  2. キューに1つ目と2つ目のタスクを非同期で追加しました。理解できない場合はパート1を参照。

  3. この時点で2つのタスクが並行キューに追加され、両方が同時に実行されています。次に、3番目のタスクを
    ディスパッチバリアとして追加すると、前のタスクの両方がその実行を完了するまで、あるいは
    完了しない限り、そのタスクは開始されず、開始されると、バリアタスクがシリアルに実行されます
    (つまり、GCDは、そのタスクを完了するまで、このキュー上のこのタスクのみを実行する)。

  4. 最後に、バリアタスクが完了するまで実行されない4つのタスクを追加しました。


図4

図5に示すように、バリア・タスクはバリア・タスクの後に追加されたタスク4がバリア・タスクの終了後に
実行されるため、並行キューではりますが、分離された状態でシリアルに実行されます。


図5

図6に示すように、ディスパッチ・バリアがどのように機能するか、大まかな図を作成しました。


図6

バリアーを使用したリード・ライターの問題解決

同時実行キューで複数のスレッドから読み書きを行っている場合、リーダライタ問題が発生します。
このリンクにある解決策では、書き込み操作をバリアとして置くことで、書き込み操作をシリアルに実行し、
読み出し操作を通常のタスクとして実行します。書き込み操作が開始されると、読み取り操作は実行されません。

コンピュータサイエンスでは、readers-writers問題は並行処理における一般的な計算問題の例です。
この問題には少なくとも3つのバリエーションがあり、多くのスレッド(データを共有する小さなプロセス)が
一度に同じ共有リソースにアクセスしようとする状況を扱います。

有益なリンク

https://riptutorial.com/ios/example/28278/dispatch-group

【翻訳元の記事】

Concurrency in Swift (Grand Central Dispatch Part 2)
https://ali-akhtar.medium.com/concurrency-in-swift-grand-central-dispatch-part-2-1b0b025ee381

Discussion