Pertanyaan Antrean serentak vs. serial di GCD


Saya berjuang untuk memahami sepenuhnya antrian bersamaan dan serial di GCD. Saya memiliki beberapa masalah dan berharap seseorang dapat menjawab saya dengan jelas dan langsung pada intinya.

  1. Saya membaca bahwa antrian serial dibuat dan digunakan untuk menjalankan tugas satu demi satu. Namun, apa yang terjadi jika:

    • Saya membuat antrean serial
    • saya menggunakan dispatch_async (pada antrean serial yang baru saya buat) tiga kali untuk mengirim tiga blok A, B, C

    Akankah tiga blok dieksekusi:

    • dalam urutan A, B, C karena antrean adalah serial

      ATAU

    • secara bersamaan (dalam waktu yang sama pada benang parralel) karena saya menggunakan pengiriman ASYNC
  2. Saya membaca yang bisa saya gunakan dispatch_sync pada antrian bersamaan untuk mengeksekusi blok satu demi satu. Dalam hal ini, MENGAPA antrian serial bahkan ada, karena saya selalu dapat menggunakan antrean bersamaan di mana saya dapat mengirim SYNCHRONOUSLY sebanyak mungkin blok yang saya inginkan?

    Terima kasih atas penjelasan yang bagus!


75
2017-10-04 10:47


asal


Jawaban:


Contoh sederhana: Anda memiliki blok yang membutuhkan waktu satu menit untuk dieksekusi. Anda menambahkannya ke antrean dari utas utama. Mari kita lihat empat kasus.

  • async - concurrent: kode berjalan di thread latar belakang. Kontrol kembali segera ke utas utama (dan UI). Blok tidak dapat mengasumsikan bahwa itu adalah satu-satunya blok yang berjalan di antrian itu
  • async - serial: kode berjalan pada utas latar belakang. Kontrol kembali segera ke utas utama. Blok itu bisa berasumsi bahwa itu satu-satunya blok yang berjalan di antrian itu
  • sync - concurrent: kode berjalan di thread latar belakang tetapi utas utama menunggu sampai selesai, memblokir pembaruan apa pun ke UI. Blok tidak dapat mengasumsikan bahwa itu adalah satu-satunya blok yang berjalan di antrian itu (saya bisa menambahkan blok lain menggunakan async beberapa detik sebelumnya)
  • sync - serial: kode berjalan di thread latar belakang tetapi utas utama menunggu sampai selesai, memblokir pembaruan apa pun ke UI. Blok itu bisa berasumsi bahwa itu satu-satunya blok yang berjalan di antrian itu

Tentunya Anda tidak akan menggunakan salah satu dari dua terakhir untuk proses yang berjalan lama. Anda biasanya melihatnya ketika Anda mencoba memperbarui UI (selalu di utas utama) dari sesuatu yang mungkin berjalan di utas lain.


138
2017-10-04 11:12



Berikut beberapa eksperimen yang telah saya lakukan untuk membuat saya mengerti tentang ini serial, concurrent antrian dengan 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")
        }
    }
}

Tugas akan berjalan di utas yang berbeda (selain utas utama) saat Anda menggunakan asinkron di GCD. Async berarti mengeksekusi baris berikutnya tidak menunggu sampai blok mengeksekusi yang menghasilkan utas utama & antrian utama non blocking.       Karena antrean serialnya, semua dijalankan dalam urutan yang ditambahkan ke antrean serial. Kasing yang dijalankan secara serial selalu dijalankan satu per satu oleh satu utas yang dikaitkan dengan Antrean.

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")
        }
    }
}

Tugas dapat berjalan di utas utama saat Anda menggunakan sinkronisasi di GCD. Sinkronisasi menjalankan blok pada antrian yang diberikan dan menunggu untuk menyelesaikan yang menghasilkan memblokir utas utama atau antrian utama. Karena antrian utama harus menunggu hingga blok yang dikirim selesai, utas utama akan tersedia untuk memproses pemblokiran dari antrian selain dari antrian utama. Oleh karena itu, ada kemungkinan kode yang mengeksekusi pada antrian latar belakang sebenarnya dapat dieksekusi pada utas utama       Karena antrean serialnya, semuanya dijalankan dalam urutan yang ditambahkan (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")
    }
}

Tugas akan berjalan di latar belakang utas ketika Anda menggunakan async di GCD. Async berarti mengeksekusi baris berikutnya tidak menunggu sampai blok mengeksekusi yang menghasilkan utas utama non-blocking.       Ingat dalam antrean bersamaan, tugas diproses dalam urutan yang ditambahkan ke antrean tetapi dengan utas yang berbeda dilampirkan pada   antre. Ingat mereka tidak seharusnya menyelesaikan tugas sebagai pesanan   mereka ditambahkan ke antrian. Pesanan tugas berbeda setiap kali   utas dibuat secara otomatis. Task dijalankan secara paralel. Dengan lebih dari   bahwa (maxConcurrentOperationCount) tercapai, beberapa tugas akan berperilaku   sebagai serial sampai seutas benang gratis.

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")
    }
}

Tugas dapat berjalan di utas utama saat Anda menggunakan sinkronisasi di GCD. Sinkronisasi menjalankan blok pada antrian yang diberikan dan menunggu untuk menyelesaikan yang menghasilkan memblokir utas utama atau antrian utama. Karena antrian utama harus menunggu hingga blok yang dikirim selesai, utas utama akan tersedia untuk memproses pemblokiran dari antrian selain dari antrian utama. Oleh karena itu ada kemungkinan kode mengeksekusi pada antrian latar belakang sebenarnya dapat dieksekusi pada utas utama.         Karena antrean bersamaan, tugas mungkin tidak selesai dalam urutan yang ditambahkan ke antrean. Tetapi dengan operasi sinkron yang dilakukannya meskipun mereka dapat diproses oleh thread yang berbeda. Jadi, berperilaku seperti ini adalah antrean serial.

Berikut ini ringkasan percobaan ini

Ingat menggunakan GCD Anda hanya menambahkan tugas ke Antrean dan melakukan tugas dari antrean itu. Antrian mengirimkan tugas Anda baik di thread utama atau latar belakang tergantung pada apakah operasi sinkron atau asinkron. Jenis antrian adalah Serial, Bersamaan, Antrean pengiriman utama. Semua tugas yang Anda lakukan dilakukan secara default dari antrian Pengiriman utama. Sudah ada empat antrian bersamaan yang telah ditetapkan untuk aplikasi Anda dan satu antrean utama (DispatchQueue.main). dapat juga secara manual membuat antrean Anda sendiri dan melakukan tugas dari antrean itu.

UI Tugas terkait harus selalu dilakukan dari utas utama dengan mengirim tugas ke antrean Utama. Utilitas tangan yang singkat adalah DispatchQueue.main.sync/async sedangkan jaringan yang terkait / operasi berat harus selalu dilakukan secara asynchronous tidak masalah yang pernah Anda gunakan thread baik utama atau latar belakang

EDIT: Namun, ada beberapa kasus yang Anda perlukan untuk melakukan operasi panggilan jaringan secara serentak di utas latar tanpa UI pembekuan (misalnya, meregangkan OAuth Token dan menunggu jika berhasil atau tidak). Anda perlu membungkus metode tersebut di dalam operasi asinkron. Dengan cara ini, operasi berat Anda dijalankan dalam urutan dan tanpa memblokir utas utama.

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: Anda dapat menonton video demo sini


82
2018-04-04 09:25



Jika saya mengerti benar tentang bagaimana GCD bekerja, saya pikir ada dua jenis DispatchQueue, serial dan concurrent, pada saat yang sama, ada dua cara bagaimana caranya DispatchQueue mengirim tugasnya, yang ditugaskan closure, yang pertama async, dan yang lainnya sync. Mereka bersama-sama menentukan bagaimana penutupan (tugas) sebenarnya dieksekusi.

Saya menemukan itu serial dan concurrentberarti berapa banyak benang yang dapat digunakan antrean, serial berarti satu, sedangkan concurrent artinya banyak. Dan sync dan async berarti tugas akan dieksekusi di mana utas, utas pemanggil atau utas yang mendasari antrean itu, sync berarti berjalan pada benang pemanggil sedangkan async berarti berjalan pada utas yang mendasarinya.

Berikut ini adalah kode eksperimental yang dapat dijalankan di taman bermain 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()

Semoga itu bisa membantu.


4