Antrean serentak vs antrean serial di GCD

117

Saya berjuang untuk memahami antrean serentak dan serial di GCD. Saya memiliki beberapa masalah dan berharap seseorang dapat menjawab saya dengan jelas dan pada intinya.

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

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

    Akankah ketiga blok tersebut dieksekusi:

    • di urutan A, B, C karena antriannya serial

      ATAU

    • secara bersamaan (dalam waktu yang sama di utas parralel) karena saya menggunakan pengiriman ASYNC
  2. Saya membaca bahwa saya dapat menggunakan dispatch_syncantrean bersamaan untuk mengeksekusi blok satu demi satu. Dalam hal ini, MENGAPA antrian serial bahkan ada, karena saya selalu dapat menggunakan antrian bersamaan di mana saya dapat mengirimkan blok secara SINKRONISASI sebanyak yang saya inginkan?

    Terima kasih atas penjelasan yang bagus!

Bogdan Alexandru
sumber

Jawaban:

216

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

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

Jelas Anda tidak akan menggunakan salah satu dari dua yang terakhir untuk proses yang berjalan lama. Anda biasanya melihatnya saat mencoba mengupdate UI (selalu di thread utama) dari sesuatu yang mungkin berjalan di thread lain.

Stephen Darlington
sumber
14
Jadi Anda memberitahu saya bahwa: (1) jenis antrian (conc atau serial) adalah SATU-SATUNYA elemen yang memutuskan apakah tugas-tugas dijalankan secara berurutan atau secara paralel ;; (2) tipe pengiriman (sync atau async) hanya mengatakan apakah eksekusi berjalan ATAU tidak menuju ke instruksi berikutnya? Maksud saya, jika saya mengirimkan tugas SINKRONISASI, kode akan diblokir sampai tugas itu selesai, tidak peduli antrian apa yang dijalankan?
Bogdan Alexandru
13
@GambarFun. Antrian menentukan kebijakan eksekusi, bukan bagaimana Anda mengantri blok. Sinkronisasi menunggu pemblokiran selesai, asinkron tidak.
Jano
2
@swiftBUTCHER Sampai titik tertentu, ya. Saat Anda membuat antrian, Anda dapat menentukan jumlah maksimum utas. Jika Anda menambahkan lebih sedikit tugas dari itu, mereka akan mengeksekusi secara paralel. Dengan lebih dari itu, beberapa tugas akan tetap dalam antrian sampai ada kapasitas yang tersedia.
Stephen Darlington
2
@PabloA., Utas utama adalah antrean serial jadi sebenarnya hanya ada dua kasus. Di luar itu, persis sama. Async segera kembali (dan blok mungkin akan dieksekusi di akhir run loop saat ini). Gotcha utama adalah jika Anda melakukan sinkronisasi dari utas utama ke utas utama, dalam hal ini Anda mendapatkan jalan buntu.
Stephen Darlington
1
@ShauketSheikh Tidak. Utas utama adalah antrean serial, tetapi tidak semua antrean serial adalah utas utama. Pada poin keempat, utas utama akan diblokir, menunggu utas lain bersaing dengan pekerjaannya. Jika antrian serial adalah utas utama, Anda akan menemui jalan buntu.
Stephen Darlington
122

Berikut adalah beberapa percobaan yang telah saya lakukan untuk membuat saya mengerti tentang ini serial, concurrentantrian 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 dijalankan di utas berbeda (selain utas utama) saat Anda menggunakan asinkron di GCD. Async berarti mengeksekusi baris berikutnya jangan menunggu sampai blok dieksekusi yang menghasilkan non-blocking main thread & main queue. Sejak antrian serialnya, semua dieksekusi sesuai urutan ditambahkan ke antrian serial. Tugas yang dijalankan secara serial selalu dijalankan satu per satu oleh utas tunggal yang terkait dengan Antrian.

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 sampai selesai yang mengakibatkan pemblokiran utas utama atau antrian utama Karena antrian utama perlu menunggu sampai blok yang dikirim selesai, utas utama akan tersedia untuk memproses blok dari antrian selain dari antrian utama. Oleh karena itu ada kemungkinan kode yang dieksekusi pada antrian latar belakang sebenarnya dapat dieksekusi pada utas utama Sejak antrian serial, semua dieksekusi dalam urutan penambahannya (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 thread latar belakang saat Anda menggunakan asinkron di GCD. Async berarti mengeksekusi baris berikutnya jangan menunggu sampai blok dieksekusi yang menghasilkan thread utama yang tidak memblokir. Ingat dalam antrian bersamaan, tugas diproses dalam urutan mereka ditambahkan ke antrian tetapi dengan utas berbeda yang dilampirkan ke antrian. Ingat mereka tidak seharusnya menyelesaikan tugas karena urutan mereka ditambahkan ke antrian. Urutan tugas berbeda setiap kali utas dibuat secara otomatis. Tugas dijalankan secara paralel. Dengan lebih dari itu (maxConcurrentOperationCount) tercapai, beberapa tugas akan berperilaku sebagai serial hingga utas bebas.

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 sampai selesai yang mengakibatkan pemblokiran utas utama atau antrian utama Karena antrian utama perlu menunggu sampai blok yang dikirim selesai, utas utama akan tersedia untuk memproses blok dari antrian selain dari main queue. Oleh karena itu ada kemungkinan kode yang dieksekusi di antrian latar belakang sebenarnya sedang dieksekusi di thread utama. Karena antreannya berbarengan, tugas mungkin tidak selesai sesuai urutan penambahannya ke antrean. Tetapi dengan operasi sinkron itu dilakukan meskipun mereka mungkin diproses oleh utas yang berbeda. Jadi, ini berperilaku karena ini adalah antrian serial.

Berikut adalah ringkasan dari eksperimen tersebut

Ingat menggunakan GCD Anda hanya menambahkan tugas ke Antrian dan melakukan tugas dari antrian itu. Antrian mengirimkan tugas Anda baik di utas utama atau latar belakang bergantung pada apakah operasi sinkron atau asinkron. Jenis antrian adalah Serial, Concurrent, Main dispatch queue. Semua tugas yang Anda lakukan dilakukan secara default dari Main dispatch queue. Sudah ada empat antrian global bersamaan yang telah ditentukan untuk digunakan aplikasi Anda dan satu antrian utama (DispatchQueue.main). Anda juga dapat secara manual membuat antrian Anda sendiri dan melakukan tugas dari antrian itu.

Tugas Terkait UI harus selalu dilakukan dari utas utama dengan mengirimkan tugas ke antrean Utama. Utilitas tangan pendek adalah DispatchQueue.main.sync/asyncsedangkan operasi terkait jaringan / berat harus selalu dilakukan secara asinkron, tidak masalah utas mana yang Anda gunakan baik utama atau latar belakang

EDIT: Namun, ada beberapa kasus Anda perlu melakukan operasi panggilan jaringan secara sinkron di thread latar belakang tanpa membekukan UI (egrefreshing OAuth Token dan menunggu apakah berhasil atau tidak). Anda perlu memasukkan metode itu ke dalam operasi asynchronous. operasi 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 di sini

LC 웃
sumber
Demonstrasi hebat .... baris berikutnya jangan menunggu sampai blok dijalankan yang menghasilkan utas utama non pemblokiran, inilah mengapa jika Anda menggunakan breakpoint pada utas latar belakang, itu akan melompat ke }karena itu benar-benar tidak dijalankan pada saat itu
Honey
@ Pria iOS yang malas itu 웃 Saya masih tidak mengerti perbedaan antara serial asinkron serentak dan asinkron. Apa implikasi dari menggunakan keduanya. Keduanya berjalan di latar belakang tidak mengganggu UI. Dan mengapa Anda pernah menggunakan sinkronisasi? Tidak semua kode disinkronkan. satu demi satu?
eonist
1
@GitSyncApp Anda dapat menonton videonya di sini
Anish Parajuli 웃
@ Pria iOS yang malas itu 웃: terima kasih telah membuatnya. Saya memposting di slack swift-lang. Akan menjadi 👌 Jika Anda bisa membuatnya tentang DispatchGroup dan DispatchWorkItem juga. : D
eonist
Saya telah diuji yang terakhir Anda, concurrentQueue.syncdari doLongSyncTaskInConcurrentQueue()fungsi, mencetak thread utama, Task will run in different threadtampaknya tidak benar.
obrolan
54

Pertama, penting untuk mengetahui perbedaan antara untaian dan antrean dan apa yang sebenarnya dilakukan GCD. Saat kami menggunakan antrean pengiriman (melalui GCD), kami benar-benar mengantri, bukan melakukan threading. Kerangka kerja Dispatch dirancang khusus untuk menjauhkan kita dari threading, karena Apple mengakui bahwa "menerapkan solusi threading yang benar [dapat] menjadi sangat sulit, jika tidak [terkadang] mustahil untuk dicapai". Oleh karena itu, untuk melakukan tugas secara bersamaan (tugas yang kami tidak ingin membekukan UI), yang perlu kita lakukan hanyalah membuat antrean tugas tersebut dan menyerahkannya ke GCD. Dan GCD menangani semua penguliran terkait. Oleh karena itu, yang sebenarnya kami lakukan hanyalah mengantri.

Hal kedua yang harus segera diketahui adalah apa itu tugas. Sebuah tugas adalah semua kode di dalam blok antrian itu (tidak di dalam antrian, karena kita bisa menambahkan sesuatu ke antrian sepanjang waktu, tapi di dalam closure tempat kita menambahkannya ke antrian). Sebuah tugas terkadang disebut sebagai blok dan blok terkadang disebut sebagai tugas (tetapi mereka lebih dikenal sebagai tugas, khususnya di komunitas Swift). Dan tidak peduli seberapa banyak atau sedikit kode, semua kode di dalam kurung kurawal dianggap sebagai satu tugas:

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
}

Dan jelas menyebutkan bahwa konkuren berarti pada saat yang sama dengan hal-hal lain dan arti serial satu demi satu (tidak pernah pada waktu yang sama). Menyerialisasikan sesuatu, atau memasukkan sesuatu ke dalam serial, berarti menjalankannya dari awal sampai akhir dalam urutannya dari kiri ke kanan, atas ke bawah, tanpa gangguan.

Ada dua jenis antrean, serial dan konkuren, tetapi semua antrean serentak relatif satu sama lain . Fakta bahwa Anda ingin menjalankan kode apa pun "di latar belakang" berarti Anda ingin menjalankannya secara bersamaan dengan utas lain (biasanya utas utama). Oleh karena itu, semua antrian pengiriman, serial atau bersamaan, menjalankan tugas mereka secara bersamaan relatif terhadap antrian lainnya . Setiap serialisasi yang dilakukan oleh antrian (dengan antrian serial), hanya berkaitan dengan tugas-tugas dalam antrian pengiriman [serial] tunggal itu (seperti dalam contoh di atas di mana ada dua tugas dalam antrian serial yang sama; tugas-tugas itu akan dijalankan satu demi satu yang lain, tidak pernah secara bersamaan).

ANTRIAN SERIAL (sering dikenal sebagai antrean pengiriman pribadi) menjamin pelaksanaan tugas satu per satu dari awal hingga selesai dalam urutan yang ditambahkan ke antrean tertentu. Ini adalah satu-satunya jaminan serialisasi di mana pun dalam diskusi antrian pengiriman--bahwa tugas tertentu dalam antrian serial tertentu dijalankan dalam serial. Antrian serial dapat, bagaimanapun, berjalan secara bersamaan dengan antrian serial lainnya jika mereka adalah antrian terpisah karena, sekali lagi, semua antrian adalah bersamaan relatif satu sama lain. Semua tugas berjalan di utas berbeda tetapi tidak setiap tugas dijamin berjalan di utas yang sama (tidak penting, tetapi menarik untuk diketahui). Dan kerangka kerja iOS tidak dilengkapi dengan antrian serial yang siap digunakan, Anda harus membuatnya. Antrian privat (non-global) adalah serial secara default, jadi untuk membuat antrian serial:

let serialQueue = DispatchQueue(label: "serial")

Anda dapat membuatnya bersamaan melalui properti atributnya:

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

Tetapi pada titik ini, jika Anda tidak menambahkan atribut lain ke antrean privat, Apple merekomendasikan agar Anda hanya menggunakan salah satu antrean global siap pakai mereka (yang semuanya bersamaan). Di bagian bawah jawaban ini, Anda akan melihat cara lain untuk membuat antrian serial (menggunakan properti target), seperti yang disarankan Apple untuk melakukannya (untuk pengelolaan sumber daya yang lebih efisien). Tapi untuk saat ini, memberi label saja sudah cukup.

ANTRIAN BERTENTANG (sering dikenal sebagai antrian pengiriman global) dapat menjalankan tugas secara bersamaan; Namun, tugas dijamin dimulai dalam urutan yang ditambahkan ke antrean tertentu, tetapi tidak seperti antrean serial, antrean tidak menunggu hingga tugas pertama selesai sebelum memulai tugas kedua. Tugas (seperti antrean serial) dijalankan pada utas berbeda dan (seperti antrean serial) tidak setiap tugas dijamin berjalan di utas yang sama (tidak penting, tetapi menarik untuk diketahui). Dan kerangka kerja iOS hadir dengan empat antrean bersamaan yang siap digunakan. Anda dapat membuat antrean bersamaan menggunakan contoh di atas atau dengan menggunakan salah satu antrean global Apple (yang biasanya disarankan):

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

RETAIN-CYCLE RESISTANT: Antrean pengiriman adalah objek yang dihitung berdasarkan referensi, tetapi Anda tidak perlu menyimpan dan melepaskan antrean global karena antrean tersebut bersifat global, sehingga dipertahankan dan rilis akan diabaikan. Anda dapat mengakses antrian global secara langsung tanpa harus menetapkannya ke properti.

Ada dua cara untuk mengirim antrian: sinkron dan asinkron.

SYNC DISPATCHING berarti bahwa thread tempat antrian dikirim (thread pemanggil) berhenti setelah mengirim antrian dan menunggu tugas di blok antrian itu selesai dieksekusi sebelum melanjutkan. Untuk mengirim secara sinkron:

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

ASYNC DISPATCHING berarti bahwa thread pemanggil terus berjalan setelah pengiriman antrian dan tidak menunggu tugas dalam blok antrian tersebut untuk menyelesaikan eksekusi. Untuk mengirim secara asinkron:

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

Sekarang orang mungkin berpikir bahwa untuk menjalankan tugas dalam serial, antrian serial harus digunakan, dan itu tidak sepenuhnya benar. Untuk menjalankan beberapa tugas secara serial, antrian serial harus digunakan, tetapi semua tugas (diisolasi sendiri) dijalankan dalam serial. Pertimbangkan contoh ini:

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

Tidak peduli bagaimana Anda mengonfigurasi (serial atau bersamaan) atau mengirim (sinkronisasi atau asinkron) antrean ini, tugas ini akan selalu dijalankan dalam serial. Loop ketiga tidak akan pernah berjalan sebelum loop kedua dan loop kedua tidak akan pernah berjalan sebelum loop pertama. Hal ini berlaku dalam antrean apa pun yang menggunakan pengiriman apa pun. Ini adalah saat Anda memperkenalkan banyak tugas dan / atau antrean di mana serial dan konkurensi benar-benar berperan.

Pertimbangkan dua antrian ini, satu serial dan satu bersamaan:

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

Katakanlah kita mengirimkan dua antrian bersamaan di async:

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

Outputnya campur aduk (seperti yang diharapkan) tetapi perhatikan bahwa setiap antrian menjalankan tugasnya sendiri dalam serial. Ini adalah contoh konkurensi paling dasar - dua tugas yang berjalan secara bersamaan di latar belakang dalam antrean yang sama. Sekarang mari kita buat serial yang pertama:

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

Bukankah antrian pertama seharusnya dijalankan secara serial? Itu (dan begitu juga yang kedua). Apa pun yang terjadi di latar belakang tidak menjadi masalah antrian. Kami memberi tahu antrian serial untuk dieksekusi dalam serial dan itu berhasil ... tetapi kami hanya memberikannya satu tugas. Sekarang mari kita berikan dua tugas:

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

Dan ini adalah contoh serialisasi yang paling dasar (dan hanya mungkin) - dua tugas yang berjalan secara serial (satu demi satu) di latar belakang (ke utas utama) dalam antrian yang sama. Tetapi jika kita menjadikannya dua antrian serial yang terpisah (karena dalam contoh di atas mereka adalah antrian yang sama), keluarannya akan bercampur aduk lagi:

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

Dan inilah yang saya maksud ketika saya mengatakan semua antrean berbarengan relatif satu sama lain. Ini adalah dua antrian serial yang menjalankan tugasnya pada saat yang sama (karena antriannya terpisah). Antrian tidak tahu atau peduli dengan antrian lainnya. Sekarang mari kembali ke dua antrian serial (dari antrian yang sama) dan tambahkan antrian ketiga, yang bersamaan:

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

Itu agak tidak terduga, mengapa antrian bersamaan menunggu antrian serial selesai sebelum dieksekusi? Itu bukan konkurensi. Taman bermain Anda mungkin menunjukkan hasil yang berbeda tetapi tempat bermain saya menunjukkan ini. Dan ini menunjukkan ini karena prioritas antrean serentak saya tidak cukup tinggi bagi GCD untuk menjalankan tugasnya lebih cepat. Jadi jika saya menjaga semuanya tetap sama tetapi mengubah QoS antrian global (kualitas layanannya, yang merupakan tingkat prioritas antrian) let concurrentQueue = DispatchQueue.global(qos: .userInteractive), maka hasilnya seperti yang diharapkan:

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

Dua antrian serial menjalankan tugas mereka dalam serial (seperti yang diharapkan) dan antrian bersamaan mengeksekusi tugasnya lebih cepat karena diberikan tingkat prioritas tinggi (QoS tinggi, atau kualitas layanan).

Dua antrian bersamaan, seperti pada contoh cetakan pertama kita, menunjukkan hasil cetakan campur aduk (seperti yang diharapkan). Untuk membuatnya dicetak dengan rapi dalam serial, kita harus membuat keduanya dalam antrian serial yang sama (contoh antrian yang sama, juga, bukan hanya label yang sama) . Kemudian setiap tugas dijalankan secara serial sehubungan dengan yang lain. Namun, cara lain untuk membuatnya dicetak secara serial adalah dengan membuatnya tetap bersamaan tetapi mengubah metode pengirimannya:

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

Ingat, sinkronisasi pengiriman hanya berarti bahwa utas panggilan menunggu sampai tugas dalam antrian selesai sebelum melanjutkan. Peringatan di sini, jelas, adalah bahwa utas panggilan dibekukan sampai tugas pertama selesai, yang mungkin atau mungkin tidak seperti yang Anda inginkan untuk dilakukan UI.

Dan karena alasan inilah kami tidak dapat melakukan hal berikut:

DispatchQueue.main.sync { ... }

Ini adalah satu-satunya kombinasi antrian dan metode pengiriman yang tidak dapat kami lakukan — pengiriman sinkron pada antrean utama. Dan itu karena kami meminta antrean utama untuk dibekukan hingga kami menjalankan tugas dalam kurung kurawal ... yang kami kirimkan ke antrean utama, yang baru saja kami bekukan. Ini disebut kebuntuan. Untuk melihatnya beraksi di taman bermain:

DispatchQueue.main.sync { // stop the main queue and wait for the following to finish
    print("hello world") // this will never execute on the main queue because we just stopped it
}
// deadlock

Satu hal terakhir yang perlu disebutkan adalah sumber daya. Saat kami memberikan tugas ke antrean, GCD menemukan antrean yang tersedia dari kumpulan yang dikelola secara internal. Sejauh penulisan jawaban ini, ada 64 antrian yang tersedia per qos. Itu mungkin terlihat banyak tetapi mereka dapat dengan cepat dikonsumsi, terutama oleh pustaka pihak ketiga, terutama kerangka kerja database. Untuk alasan ini, Apple memiliki rekomendasi tentang manajemen antrian (disebutkan dalam tautan di bawah); satu makhluk:

Daripada membuat antrean serentak pribadi, kirimkan tugas ke salah satu antrean pengiriman serentak global. Untuk tugas serial, setel target antrian serial Anda ke salah satu antrian serentak global. Dengan begitu, Anda dapat mempertahankan perilaku antrian serial sambil meminimalkan jumlah antrian terpisah yang membuat utas.

Untuk melakukan ini, alih-alih membuatnya seperti yang kami lakukan sebelumnya (yang masih bisa Anda lakukan), Apple merekomendasikan membuat antrean serial seperti ini:

let serialQueue = DispatchQueue(label: "serialQueue", qos: .default, attributes: [], autoreleaseFrequency: .inherit, target: .global(qos: .default))

Untuk bacaan lebih lanjut, saya merekomendasikan yang berikut ini:

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

https://developer.apple.com/documentation/dispatch/dispatchqueue

bsod
sumber
7

Jika saya benar-benar memahami cara kerja GCD, menurut saya ada dua jenis DispatchQueue, serialdan concurrent, pada saat yang sama, ada dua cara bagaimana DispatchQueuemengirimkan tugasnya, yang ditetapkan closure, yang pertama async, dan yang lainnya sync. Semuanya bersama-sama menentukan bagaimana closure (tugas) sebenarnya dijalankan.

Saya menemukan bahwa serialdan concurrentberarti berapa banyak utas yang dapat digunakan antrian, serialberarti satu, sedangkan concurrentberarti banyak. Dan syncdan asyncberarti tugas akan dijalankan di mana benang, benang pemanggil atau benang yang mendasari antrian itu, syncberarti berjalan di thread pemanggil sedangkan asyncsarana dijalankan pada thread yang mendasari.

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 bisa bermanfaat.

Keith
sumber
7

Saya suka berpikir ini menggunakan metafora ini (Ini tautan ke gambar aslinya):

Ayah akan butuh bantuan

Bayangkan ayahmu sedang mencuci piring dan kamu baru saja minum segelas soda. Anda membawa gelas itu ke ayah Anda untuk membersihkannya, meletakkannya di samping piring lainnya.

Sekarang ayahmu mencuci piring sendirian, jadi dia harus melakukannya satu per satu: Ayahmu di sini mewakili antrian serial .

Tetapi Anda tidak terlalu tertarik untuk berdiri di sana dan menontonnya dibersihkan. Jadi, Anda menjatuhkan gelasnya, dan kembali ke kamar Anda: ini disebut pengiriman asinkron . Ayah Anda mungkin atau mungkin tidak memberi tahu Anda begitu dia selesai, tetapi yang penting adalah Anda tidak menunggu gelas dibersihkan; Anda kembali ke kamar Anda untuk melakukan, Anda tahu, hal-hal anak-anak.

Sekarang anggaplah Anda masih haus dan ingin minum air pada gelas yang sama yang menjadi favorit Anda, dan Anda benar-benar menginginkannya kembali segera setelah dibersihkan. Jadi, Anda berdiri di sana dan melihat ayah Anda mencuci piring sampai Anda selesai. Ini adalah pengiriman sinkronisasi , karena Anda diblokir saat Anda menunggu tugas selesai.

Dan terakhir, katakanlah ibumu memutuskan untuk membantu ayahmu dan bergabung dengannya mencuci piring. Sekarang antrian tersebut menjadi antrian bersamaan karena mereka dapat membersihkan banyak piring pada waktu yang bersamaan; tetapi perhatikan bahwa Anda masih dapat memutuskan untuk menunggu di sana atau kembali ke kamar Anda, terlepas dari cara kerjanya.

Semoga ini membantu

Yunus Nedim Mehel
sumber
3

1. Saya membaca bahwa antrian serial dibuat dan digunakan untuk menjalankan tugas satu demi satu. Namun, apa yang terjadi jika: - • Saya membuat antrian serial • Saya menggunakan dispatch_async (pada antrian serial yang baru saja saya buat) tiga kali untuk mengirimkan tiga blok A, B, C

JAWABAN : - Ketiga blok dieksekusi satu demi satu. Saya telah membuat satu kode sampel yang membantu untuk memahami.

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)
    }
}
CrazyPro007
sumber