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.
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
Saya membaca bahwa saya dapat menggunakan
dispatch_sync
antrean 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!
ios
multithreading
concurrency
grand-central-dispatch
Bogdan Alexandru
sumber
sumber
Jawaban:
Contoh sederhana: Anda memiliki blok yang membutuhkan satu menit untuk dieksekusi. Anda menambahkannya ke antrian dari utas utama. Mari kita lihat empat kasus.
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.
sumber
Berikut adalah beberapa percobaan yang telah saya lakukan untuk membuat saya mengerti tentang ini
serial
,concurrent
antrian denganGrand Central Dispatch
.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/async
sedangkan operasi terkait jaringan / berat harus selalu dilakukan secara asinkron, tidak masalah utas mana yang Anda gunakan baik utama atau latar belakangEDIT: 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.
EDIT EDIT: Anda dapat menonton video demo di sini
sumber
}
karena itu benar-benar tidak dijalankan pada saat ituconcurrentQueue.sync
daridoLongSyncTaskInConcurrentQueue()
fungsi, mencetak thread utama,Task will run in different thread
tampaknya tidak benar.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:
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:
Anda dapat membuatnya bersamaan melalui properti atributnya:
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):
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:
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:
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:
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:
Katakanlah kita mengirimkan dua antrian bersamaan di async:
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:
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:
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:
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:
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: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:
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:
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:
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:
Untuk melakukan ini, alih-alih membuatnya seperti yang kami lakukan sebelumnya (yang masih bisa Anda lakukan), Apple merekomendasikan membuat antrean serial seperti ini:
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
sumber
Jika saya benar-benar memahami cara kerja GCD, menurut saya ada dua jenis
DispatchQueue
,serial
danconcurrent
, pada saat yang sama, ada dua cara bagaimanaDispatchQueue
mengirimkan tugasnya, yang ditetapkanclosure
, yang pertamaasync
, dan yang lainnyasync
. Semuanya bersama-sama menentukan bagaimana closure (tugas) sebenarnya dijalankan.Saya menemukan bahwa
serial
danconcurrent
berarti berapa banyak utas yang dapat digunakan antrian,serial
berarti satu, sedangkanconcurrent
berarti banyak. Dansync
danasync
berarti tugas akan dijalankan di mana benang, benang pemanggil atau benang yang mendasari antrian itu,sync
berarti berjalan di thread pemanggil sedangkanasync
sarana dijalankan pada thread yang mendasari.Berikut ini adalah kode eksperimental yang dapat dijalankan di taman bermain Xcode.
Semoga bisa bermanfaat.
sumber
Saya suka berpikir ini menggunakan metafora ini (Ini tautan ke gambar aslinya):
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
sumber
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.
sumber