web-dev-qa-db-ja.com

GCDの同時キューとシリアルキュー

GCDの並行キューとシリアルキューを完全に理解するのに苦労しています。私にはいくつかの問題があり、誰かが明確にその時点で答えてくれることを望んでいます。

  1. 私は、タスクを次々に実行するためにシリアルキューが作成され使用されることを読んでいます。ただし、次の場合はどうなりますか?

    • シリアルキューを作成します
    • dispatch_async(作成したシリアルキュー)を3回使用して、3つのブロックA、B、Cをディスパッチします。

    3つのブロックが実行されます:

    • キューがシリアルであるため、A、B、Cの順序で

      OR

    • aSYNCディスパッチを使用したため、同時に(パラレルスレッドで)
  2. ブロックを次々に実行するために、並行キューでdispatch_syncを使用できることを読んでいます。その場合、必要な数のブロックをSYNCHRONOUSLYでディスパッチできる並行キューを常に使用できるので、なぜシリアルキューが存在するのでしょうか。

    良い説明をありがとう!

100

簡単な例:実行に1分かかるブロックがあります。メインスレッドからキューに追加します。 4つのケースを見てみましょう。

  • async-同時:コードはバックグラウンドスレッドで実行されます。制御はすぐにメインスレッド(およびUI)に戻ります。ブロックは、そのキューで実行されている唯一のブロックであると想定することはできません
  • async-serial:コードはバックグラウンドスレッドで実行されます。制御はすぐにメインスレッドに戻ります。ブロックcanは、そのキューで実行されている唯一のブロックであると想定します
  • sync-同時:コードはバックグラウンドスレッドで実行されますが、メインスレッドは終了するまで待機し、UIの更新をブロックします。ブロックは、それがそのキューで実行されている唯一のブロックであると想定することはできません(数秒前に非同期を使用して別のブロックを追加できた可能性があります)
  • sync-シリアル:コードはバックグラウンドスレッドで実行されますが、メインスレッドは終了するまで待機し、UIの更新をブロックします。ブロックcanは、そのキューで実行されている唯一のブロックであると想定します

明らかに、長時間実行されるプロセスには最後の2つは使用しません。通常、別のスレッドで実行されている可能性のあるものからUIを(常にメインスレッドで)更新しようとしているときに表示されます。

181

これらのserialconcurrentキューとGrand Central Dispatchを理解するために行ったいくつかの実験を次に示します。

 func doLongAsyncTaskInSerialQueue() {

   let serialQueue = DispatchQueue(label: "com.queue.Serial")
      for i in 1...5 {
        serialQueue.async {

            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
    }
}

GCDで非同期を使用すると、タスクは別のスレッド(メインスレッド以外)で実行されます。非同期とは、ブロックが実行されるまで次の行を実行しないことを意味します。これにより、非スレッドのメインスレッドとメインキューが生成されます。シリアルキューなので、すべてはシリアルキューに追加された順序で実行されます。シリアルで実行されるタスクは、キューに関連付けられた単一のスレッドによって常に一度に1つずつ実行されます。

func doLongSyncTaskInSerialQueue() {
    let serialQueue = DispatchQueue(label: "com.queue.Serial")
    for i in 1...5 {
        serialQueue.sync {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
    }
}

GCDで同期を使用すると、タスクがメインスレッドで実行される場合があります。同期は、特定のキューでブロックを実行し、ブロックが完了するまで待機し、その結果、メインスレッドまたはメインキューをブロックします。メインキューは、ディスパッチされたブロックが完了するまで待機する必要があるため、メインスレッドは、メインキュー。したがって、バックグラウンドキューで実行されているコードがメインスレッドで実際に実行されている可能性があります。シリアルキューなので、すべてが追加された順に実行されます(FIFO)。

func doLongASyncTaskInConcurrentQueue() {
    let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    for i in 1...5 {
        concurrentQueue.async {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
        print("\(i) executing")
    }
}

GCDで非同期を使用すると、タスクはバックグラウンドスレッドで実行されます。非同期とは、ブロックが実行されるまで次の行を実行しないことを意味し、その結果、非ブロッキングメインスレッドが発生します。並行キューでは、タスクはキューに追加された順序で処理されますが、異なるスレッドがキューに接続されています。タスクがキューに追加される順序でタスクを終了することは想定されていないことに注意してください。タスクの順序は、スレッドが必然的に自動的に作成されるたびに異なります。それ以上(maxConcurrentOperationCount)に達すると、スレッドが解放されるまで、一部のタスクはシリアルとして動作します。

func doLongSyncTaskInConcurrentQueue() {
  let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    for i in 1...5 {
        concurrentQueue.sync {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
        print("\(i) executed")
    }
}

GCDで同期を使用すると、タスクがメインスレッドで実行される場合があります。同期は、特定のキューでブロックを実行し、ブロックが完了するまで待機し、その結果、メインスレッドまたはメインキューをブロックします。メインキューは、ディスパッチされたブロックが完了するまで待機する必要があるため、メインスレッドは、メインキュー。したがって、バックグラウンドキューで実行されているコードが実際にメインスレッドで実行されている可能性があります。並行キューなので、タスクはキューに追加された順序で終了しない場合があります。ただし、同期操作では、異なるスレッドによって処理される場合があります。したがって、これはシリアルキューとして動作します。

これがこの実験の要約です

GCDを使用して、タスクをキューに追加し、そのキューからタスクを実行するだけであることを忘れないでください。キューは、操作が同期か非同期かに応じて、メインスレッドまたはバックグラウンドスレッドでタスクをディスパッチします。キューのタイプは、Serial、Concurrent、Mainディスパッチキューです。実行するすべてのタスクは、デフォルトでMainディスパッチキューから実行されます。アプリケーションで使用する4つの定義済みグローバルコンカレントキューと1つのメインキュー(DispatchQueue.main)が既にあります。独自のキューを手動で作成し、そのキューからタスクを実行することもできます。

UI関連タスクは、メインキューにタスクをディスパッチすることにより、常にメインスレッドから実行する必要があります。ショートハンドユーティリティはDispatchQueue.main.sync/asyncであるのに対し、ネットワーク関連/重い操作は、どのスレッドであっても常に非同期に実行する必要がありますメインまたはバックグラウンドのいずれかを使用しています

編集:ただし、UIをフリーズせずにバックグラウンドスレッドでネットワークコール操作を同期的に実行する必要がある場合があります(例:OAuthトークンを更新し、成功するかどうかを待つ)このメソッドを非同期操作内にラップする必要があります。これにより、重い操作がメインスレッドをブロックせずに順番に実行されます。

func doMultipleSyncTaskWithinAsynchronousOperation() {
    let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    concurrentQueue.async {
        let concurrentQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
        for i in 1...5 {
            concurrentQueue.sync {
                let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
                let _ = try! Data(contentsOf: imgURL)
                print("\(i) completed downloading")
            }
            print("\(i) executed")
        }
    }
}

EDIT EDIT:デモビデオを見ることができます こちら

102
LC 웃

まず、スレッドとキューの違いとGCDが実際に行うことを知ることが重要です。 (GCDを介して)ディスパッチキューを使用する場合、スレッドではなく、実際にキューに入れています。 Appleが「正しいスレッド化ソリューションを実装することは、達成することが不可能ではないにしても、非常に困難になる可能性がある」と認めているように、Dispatchフレームワークは、スレッド化を回避するために特別に設計されました。したがって、タスク(UIをフリーズさせたくないタスク)を同時に実行するには、それらのタスクのキューを作成してGCDに渡すだけです。また、GCDは関連するすべてのスレッドを処理します。したがって、実際に実行しているのはキューイングだけです。

すぐに知っておくべき2番目のことは、タスクとは何かです。タスクは、そのキューブロック内のすべてのコードです(キュー内ではなく、常にキューに物事を追加できるためですが、キューに追加したクロージャー内)。タスクはブロックと呼ばれることもあり、ブロックはタスクと呼ばれることもあります(ただし、特にSwiftコミュニティでは、タスクと呼ばれます)。また、コードの量に関係なく、中括弧内のすべてのコードは単一のタスクと見なされます。

serialQueue.async {
    // this is one task
    // it can be any number of lines with any number of methods
}
serialQueue.async {
    // this is another task added to the same queue
    // this queue now has two tasks
}

キューには、シリアルと同時の2つのタイプがありますが、すべてのキューは相互に関連して同時です。 「バックグラウンドで」コードを実行するということは、別のスレッド(通常はメインスレッド)と同時に実行することを意味します。したがって、すべてのディスパッチキューは、シリアルまたはコンカレントで、他のキューに対してタスクを同時に実行します。キュー(シリアルキュー)によって実行されるすべてのシリアル化は、その単一の[シリアル]ディスパッチキュー内のタスク(同じシリアルキュー内に2つのタスクがある上記の例のように、それらのタスクは1回実行されます)もう一方、同時にはできません)。

シリアルキュー(プライベートディスパッチキューとも呼ばれます)は、タスクに追加された順序で、開始から終了まで一度に1つずつタスクの実行を保証します特定のキュー。 これは、ディスパッチキューの説明のどこでも、シリアル化の唯一の保証です---特定のシリアルキュー内の特定のタスクがシリアルで実行されること。ただし、シリアルキューは別のキューである場合、他のシリアルキューと同時に実行できます。これは、すべてのキューが相互に関連して同時に実行されるためです。すべてのタスクは個別のスレッドで実行されますが、すべてのタスクが同じスレッドで実行されることが保証されているわけではありません(重要ではありませんが、知っておくと面白いです)。また、iOSフレームワークにはすぐに使用できるシリアルキューが付属していないため、作成する必要があります。プライベート(非グローバル)キューはデフォルトでシリアルなので、シリアルキューを作成するには:

let serialQueue = DispatchQueue(label: "serial")

属性プロパティを使用して、同時実行できます。

let concurrentQueue = DispatchQueue(label: "concurrent", attributes: [.concurrent])

ただし、この時点で、プライベートキューに他の属性を追加しない場合、Appleは、すぐに使用できるグローバルキューのいずれか(すべて同時)を使用することをお勧めします。

同時キュー(グローバルディスパッチキューとして知られている)は、タスクを同時に実行できます。ただし、タスクは特定のキューに追加された順序でinitiateされることが保証されていますが、シリアルキューとは異なり、キューは最初のタスクが完了するまで待機しません2番目のタスクを開始します。タスク(シリアルキューの場合)は個別のスレッドで実行され、(シリアルキューの場合)すべてのタスクが同じスレッドで実行されることが保証されているわけではありません(重要ではありませんが、興味深いものです)。また、iOSフレームワークには、すぐに使用できる4つの並行キューが付属しています。上記の例を使用して並行キューを作成するか、Appleのグローバルキューのいずれかを使用することができます(通常は推奨されます)

let concurrentQueue = DispatchQueue.global(qos: .default)

RETAIN-CYCLE RESISTANT:ディスパッチキューは参照カウントされたオブジェクトですが、グローバルキューはグローバルであるため、保持およびリリースする必要はありません。したがって、保持およびリリースは無視されます。グローバルキューをプロパティに割り当てることなく、直接アクセスできます。

キューをディスパッチするには、同期的方法と非同期的方法の2つの方法があります。

同期ディスパッチングは、キューがディスパッチされた後にキューがディスパッチされたスレッド(呼び出しスレッド)が一時停止し、そのキューブロック内のタスクが完了するのを待つことを意味します再開する前に実行します。同期的にディスパッチするには:

DispatchQueue.global(qos: .default).sync {
    // task goes in here
}

非同期ディスパッチングは、キューをディスパッチした後も呼び出しスレッドの実行を継続し、そのキューブロック内のタスクの実行が完了するのを待たないことを意味します。非同期にディスパッチするには:

DispatchQueue.global(qos: .default).async {
    // task goes in here
}

今では、タスクをシリアルで実行するにはシリアルキューを使用する必要があると考えるかもしれませんが、それは正しくありません。 multipleタスクをシリアルで実行するには、シリアルキューを使用する必要がありますが、すべてのタスク(単独で分離)はシリアルで実行されます。この例を考えてみましょう:

whichQueueShouldIUse.syncOrAsync {

    for i in 1...10 {
        print(i)
    }
    for i in 1...10 {
        print(i + 100)
    }
    for i in 1...10 {
        print(i + 1000)
    }

}

このキューをどのように構成するか(シリアルまたは同時)またはディスパッチ(同期または非同期)に関係なく、このタスクは常にシリアルで実行されます。3番目のループは2番目のループの前に実行されず、2番目のループは最初のループの前に実行されません。これは、ディスパッチを使用するすべてのキューに当てはまります。複数のタスクやキューを導入すると、シリアルと並行性が実際に作用します。

これらの2つのキュー、1つのシリアルと1つの同時実行を検討してください。

let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue.global(qos: .default)

非同期で2つの並行キューをディスパッチするとします。

concurrentQueue.async {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
101
2
102
103
3
104
4
105
5

それらの出力は(予想どおり)ごちゃ混ぜになっていますが、各キューが独自のタスクをシリアルで実行していることに注意してください。これは、同時実行の最も基本的な例です。2つのタスクが同じキューのバックグラウンドで同時に実行されます。最初のシリアルを作成しましょう:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

101
1
2
102
3
103
4
104
5
105

最初のキューはシリアルで実行されるはずではありませんか?それはそうでした(2番目もそうでした)。バックグラウンドで他に何が起こっても、キューには関係ありません。シリアルキューにシリアルで実行するように指示しましたが、実行しましたが、タスクは1つしか与えませんでした。次に、2つのタスクを示します。

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
2
3
4
5
101
102
103
104
105

そして、これは最も基本的な(そして唯一可能な)シリアル化の例です。つまり、2つのタスクが同じキューのバックグラウンド(メインスレッド)でシリアルに(次々に)実行されます。ただし、2つの別々のシリアルキューを作成した場合(上記の例では同じキューであるため)、それらの出力は再び混乱します。

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue2.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
101
2
102
3
103
4
104
5
105

これは、すべてのキューが相互に並行していると言ったときの意味です。これらは、別々のキューであるため、同時にタスクを実行する2つのシリアルキューです。キューは他のキューを認識したり、気にしたりしません。次に、(同じキューの)2つのシリアルキューに戻り、3つ目のキュー(並行キュー)を追加します。

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 1000)
    }
}

1
2
3
4
5
101
102
103
104
105
1001
1002
1003
1004
1005

それは一種の予想外のことです。なぜ並行キューは、シリアルキューが実行される前に完了するのを待っていたのですか?それは同時性ではありません。あなたの遊び場は異なる出力を表示するかもしれませんが、私のものはこれを示しました。これは、GCDがタスクをより早く実行するのに並行キューの優先度が十分に高くなかったためです。したがって、すべてを同じに保ちながら、グローバルキューのQoS(キューの優先レベルであるサービス品質)let concurrentQueue = DispatchQueue.global(qos: .userInteractive)を変更すると、出力は期待どおりになります。

1
1001
1002
1003
2
1004
1005
3
4
5
101
102
103
104
105

2つのシリアルキューはタスクをシリアルで(予想どおり)実行し、同時キューはタスクに高い優先度レベル(高いQoSまたはサービス品質)が与えられたため、タスクをより速く実行しました。

最初の印刷の例のように、2つの同時キューはごちゃ混ぜの印刷結果を示します(予想どおり)。シリアルできれいに印刷するには、両方をシリアルキューにし、同じラベルを付けて同じキューにする必要があります。次に、各タスクが他のタスクに対して順次実行されます。ただし、それらをシリアルで印刷するための別の方法は、両方を同時に維持し、ディスパッチ方法を変更することです。

concurrentQueue.sync {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
2
3
4
5
101
102
103
104
105

同期ディスパッチとは、キュー内のタスクが完了するまで呼び出しスレッドが先に進むことを意味しないことを忘れないでください。ここでの注意点は、明らかに、最初のタスクが完了するまで呼び出しスレッド(この場合はメインスレッド)がフリーズすることです。これは、UIの実行方法に関係ない場合があります。これは、シリアル化を求めるプログラマーにとってはあまり一般的なソリューションではありませんが、その用途はあります。非常に短い、多くの場合、気付かない瞬間にUIをフリーズしたい場合があります重要で短いイベントの途中)。私はもっ​​と多くの例を見ることができますが、あなたは今までにアイデアを得る必要があります。

https://developer.Apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//Apple_ref/doc/uid/TP40008091-CH1-SW1

21
bsod

GCDの動作について正しく理解している場合、DispatchQueueserial、およびconcurrentの2つのタイプがあると同時に、DispatchQueueディスパッチには2つの方法があると思いますそのタスク、割り当てられたclosure、最初の1つはasync、もう1つはsyncです。これらが一緒になって、クロージャー(タスク)が実際に実行される方法を決定します。

serialconcurrentは、キューが使用できるスレッドの数を意味し、serialは1つのスレッドを意味し、concurrentは多くのスレッドを意味します。 syncおよびasyncは、タスクが実行されるスレッド、呼び出し元のスレッド、またはそのキューの基礎となるスレッドを意味します。syncは呼び出し元のスレッドで実行することを意味し、asyncは実行を意味します基礎となるスレッド上。

以下は、Xcodeプレイグラウンドで実行できる実験的なコードです。

PlaygroundPage.current.needsIndefiniteExecution = true
let cq = DispatchQueue(label: "concurrent.queue", attributes: .concurrent)
let cq2 = DispatchQueue(label: "concurent.queue2", attributes: .concurrent)
let sq = DispatchQueue(label: "serial.queue")

func codeFragment() {
  print("code Fragment begin")
  print("Task Thread:\(Thread.current.description)")
  let imgURL = URL(string: "http://stackoverflow.com/questions/24058336/how-do-i-run-asynchronous-callbacks-in-playground")!
  let _ = try! Data(contentsOf: imgURL)
  print("code Fragment completed")
}

func serialQueueSync() { sq.sync { codeFragment() } }
func serialQueueAsync() { sq.async { codeFragment() } }
func concurrentQueueSync() { cq2.sync { codeFragment() } }
func concurrentQueueAsync() { cq2.async { codeFragment() } }

func tasksExecution() {
  (1...5).forEach { (_) in
    /// Using an concurrent queue to simulate concurent task executions.
    cq.async {
      print("Caller Thread:\(Thread.current.description)")
      /// Serial Queue Async, tasks run serially, because only one thread that can be used by serial queue, the underlying thread of serial queue.
      //serialQueueAsync()
      /// Serial Queue Sync, tasks run serially, because only one thread that can be used by serial queue,one by one of the callers' threads.
      //serialQueueSync()
      /// Concurrent Queue Async, tasks run concurrently, because tasks can run on different underlying threads
      //concurrentQueueAsync()
      /// Concurrent Queue Sync, tasks run concurrently, because tasks can run on different callers' thread
      //concurrentQueueSync()
    }
  }
}
tasksExecution()

それが役に立てば幸いです。

4
Keith

私はこの比thisを使用してこれを考えるのが好きです(元の画像への リンク です):

Dad's gonna need some help

お父さんが料理をしていて、ソーダを一杯飲んだと想像してみましょう。グラスをお父さんに持って行き、片付けて、他の料理と並べます。

今、あなたのお父さんは自分で料理をしているので、彼はそれらを一つずつやらなければなりません:あなたのお父さんはここでシリアルキューを表します。

しかし、あなたはそこに立って、それがきれいになるのを見ることにあまり興味がありません。ガラスを落とし、部屋に戻ります。これはasync dispatchと呼ばれます。あなたのお父さんは、彼が終わったらあなたに知らせるかもしれないし、そうでないかもしれませんが、重要なことは、あなたがグラスがきれいになるのを待っていないということです。あなたはあなたの部屋に戻って、子供のものをします。

今、あなたはまだのどが渇いていて、同じグラスにお気に入りの水を入れたいと思って、きれいになったらすぐに元に戻したいとしましょう。だから、あなたはそこに立って、あなたのお父さんがあなたの料理が終わるまで料理をするのを見ます。これはsyncディスパッチです。タスクが完了するのを待っている間はブロックされているからです。

そして最後に、あなたのお母さんがあなたのお父さんを手伝うことを決めて、彼と一緒に料理をしているとしましょう。これで、キューはconcurrent queueになります。複数のディッシュを同時にクリーニングできるためです。ただし、どのように機能するかに関係なく、そこで待つか、部屋に戻るかを決定することができます。

お役に立てれば

2

1。シリアルキューが作成され、タスクを次々に実行するために使用されることを読んでいます。しかし、次の場合はどうなりますか。-•シリアルキューを作成します。 created)3回、3つのブロックA、B、Cをディスパッチします

ANSWER:-3つのブロックすべてが順番に実行されます。理解しやすいサンプルコードを1つ作成しました。

let serialQueue = DispatchQueue(label: "SampleSerialQueue")
//Block first
serialQueue.async {
    for i in 1...10{
        print("Serial - First operation",i)
    }
}

//Block second
serialQueue.async {
    for i in 1...10{
        print("Serial - Second operation",i)
    }
}
//Block Third
serialQueue.async {
    for i in 1...10{
        print("Serial - Third operation",i)
    }
}
0
CrazyPro007