Öncelikle, iş parçacıkları ve kuyruklar arasındaki farkı ve GCD'nin gerçekte ne yaptığını bilmek önemlidir. Gönderi sıralarını kullandığımızda (GCD aracılığıyla), iş parçacığı değil, gerçekten sıraya giriyoruz. Apple, "doğru bir diş açma çözümünün uygulanması, elde edilmesi [bazen] imkansız olmasa da son derece zor hale gelebileceğini" kabul ettiği için, Dispatch çerçevesi özellikle bizi iş parçacığından uzaklaştırmak için tasarlandı. Bu nedenle, görevleri eşzamanlı olarak gerçekleştirmek için (kullanıcı arayüzünün dondurulmasını istemediğimiz görevler), tek yapmamız gereken bu görevlerden bir sıra oluşturmak ve bunu GCD'ye teslim etmektir. Ve GCD, ilişkili tüm iş parçacığı işlemlerini yönetir. Bu nedenle, gerçekten yaptığımız tek şey sıraya girmek.
Hemen bilmeniz gereken ikinci şey, görevin ne olduğudur. Bir görev, o kuyruk bloğu içindeki kodun tümüdür (kuyruk içinde değil, çünkü her zaman bir kuyruğa bir şeyler ekleyebiliriz, ancak kuyruğa eklediğimiz kapanış içinde). Bir göreve bazen bir blok denir ve bir blok bazen bir görev olarak adlandırılır (ancak bunlar, özellikle Swift topluluğunda daha yaygın olarak görevler olarak bilinir). Ve ne kadar çok veya az kod olursa olsun, kaşlı ayraçlar içindeki kodun tamamı tek bir görev olarak kabul edilir:
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
}
Ve eşzamanlı ifadenin diğer şeylerle aynı anda ve dizinin birbiri ardına (asla aynı anda değil) anlamına geldiği açıktır. Bir şeyi serileştirmek ya da bir şeyi seri hale getirmek, sadece onu baştan sona, sırasına göre soldan sağa, yukarıdan aşağıya, kesintisiz olarak yürütmek demektir.
Seri ve eşzamanlı olmak üzere iki tür kuyruk vardır , ancak tüm kuyruklar birbirine göre eşzamanlıdır . Herhangi bir kodu "arka planda" çalıştırmak istemeniz, onu başka bir iş parçacığı (genellikle ana iş parçacığı) ile aynı anda çalıştırmak istediğiniz anlamına gelir. Bu nedenle, tüm gönderme kuyrukları, seri veya eşzamanlı olarak, görevlerini diğer kuyruklara göre eşzamanlı olarak yürütür. . Kuyruklar tarafından gerçekleştirilen herhangi bir serileştirme (seri kuyruklar ile), yalnızca o tek [seri] gönderim kuyruğundaki görevlerle ilgilidir (yukarıdaki örnekte olduğu gibi aynı seri kuyruğunda iki görev vardır; bu görevler birbiri ardına yürütülecektir. diğeri, asla aynı anda).
SERİ KUYRUKLAR (genellikle özel gönderim kuyrukları olarak bilinir), görevlerin bu belirli kuyruğa eklenme sırasına göre baştan sona teker teker yürütülmesini garanti eder. Bu, gönderim sıraları tartışmasının herhangi bir yerinde serileştirmenin tek garantisidir.- belirli bir seri kuyruğundaki belirli görevlerin seri olarak yürütülmesi. Bununla birlikte, seri kuyruklar ayrı kuyruklarsa diğer seri kuyruklarla aynı anda çalışabilir çünkü yine tüm kuyruklar birbirine göre eşzamanlıdır. Tüm görevler farklı iş parçacıkları üzerinde çalışır, ancak her görevin aynı iş parçacığı üzerinde çalışması garanti edilmez (önemli değil, ancak bilinmesi ilginç). Ve iOS çerçevesi herhangi bir kullanıma hazır seri kuyrukla gelmez, bunları yapmanız gerekir. Özel (global olmayan) kuyruklar varsayılan olarak seridir, bu nedenle bir seri kuyruk oluşturmak için:
let serialQueue = DispatchQueue(label: "serial")
Öznitelik özelliği aracılığıyla eşzamanlı yapabilirsiniz:
let concurrentQueue = DispatchQueue(label: "concurrent", attributes: [.concurrent])
Ancak bu noktada, özel kuyruğa başka nitelikler eklemiyorsanız, Apple, kullanıma hazır genel kuyruklarından (hepsi eşzamanlı) birini kullanmanızı önerir. Bu cevabın altında, Apple'ın bunu yapmayı önerdiği (daha verimli kaynak yönetimi için) seri kuyruklar oluşturmanın (hedef özelliği kullanarak) başka bir yolunu göreceksiniz. Ama şimdilik etiketlemek yeterli.
CONCURRENT QUEUES (genellikle genel gönderim kuyrukları olarak bilinir) görevleri eşzamanlı olarak yürütebilir; görevler, ancak, garanti edilir başlatmak onlar bu özel kuyruğuna eklenen bu sırayla fakat seri kuyruklar aksine, sıra ikinci bir görevi başlamadan önce bitirmek için ilk görev için beklemez. Görevler (seri kuyruklarda olduğu gibi) farklı iş parçacıkları üzerinde çalışır ve (seri kuyruklarda olduğu gibi) her görevin aynı iş parçacığı üzerinde çalışması garanti edilmez (önemli değil, ancak bilinmesi ilginç). Ve iOS çerçevesi, dört kullanıma hazır eşzamanlı kuyrukla birlikte gelir. Yukarıdaki örneği kullanarak veya Apple'ın genel kuyruklarından birini (genellikle önerilir) kullanarak eşzamanlı bir sıra oluşturabilirsiniz:
let concurrentQueue = DispatchQueue.global(qos: .default)
KALAN DÖNGÜSÜNE DAYANIKLI: Gönderim kuyrukları referans sayılan nesnelerdir, ancak bunlar global oldukları için global kuyrukları tutmanıza ve serbest bırakmanıza gerek yoktur ve bu nedenle alıkoyma ve bırakma yok sayılır. Global kuyruklara, onları bir özelliğe atamak zorunda kalmadan doğrudan erişebilirsiniz.
Sıraları göndermenin iki yolu vardır: eşzamanlı ve eşzamansız.
SYNC DISPATCHING , kuyruğun gönderildiği iş parçacığının (çağıran iş parçacığı) kuyruğu gönderdikten sonra durakladığı ve devam etmeden önce bu kuyruk bloğundaki görevin yürütülmesini bitirmesini beklediği anlamına gelir. Eşzamanlı olarak göndermek için:
DispatchQueue.global(qos: .default).sync {
// task goes in here
}
ASYNC DISPATCHING , çağıran iş parçacığının kuyruğu gönderdikten sonra çalışmaya devam etmesi ve bu kuyruk bloğundaki görevin yürütmeyi bitirmesini beklememesi anlamına gelir. Eşzamansız olarak göndermek için:
DispatchQueue.global(qos: .default).async {
// task goes in here
}
Şimdi, bir görevi seri olarak yürütmek için bir seri kuyruğun kullanılması gerektiği düşünülebilir ve bu tam olarak doğru değildir. Birden fazla görevi seri olarak yürütmek için , bir seri kuyruk kullanılmalıdır, ancak tüm görevler (kendileri tarafından izole edilmiş) seri olarak yürütülür. Şu örneği düşünün:
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)
}
}
Bu kuyruğu nasıl yapılandırırsanız (seri veya eşzamanlı olarak) veya gönderirseniz (eşitler veya eşzamansız), bu görev her zaman seri olarak yürütülür. Üçüncü döngü asla ikinci döngüden önce çalışmaz ve ikinci döngü asla birinci döngüden önce çalışmaz. Bu, herhangi bir gönderimi kullanan herhangi bir kuyruk için geçerlidir. Seri ve eşzamanlılığın gerçekten devreye girdiği birden fazla görev ve / veya kuyruk tanıttığınız zamandır.
Bir seri ve bir eşzamanlı olmak üzere şu iki kuyruğu düşünün:
let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue.global(qos: .default)
Eşzamansız olarak iki eşzamanlı kuyruk gönderdiğimizi varsayalım:
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
Çıktıları karışıktır (beklendiği gibi) ancak her sıranın kendi görevini seri olarak yürüttüğüne dikkat edin. Bu eşzamanlılığın en temel örneğidir - aynı kuyrukta arka planda aynı anda çalışan iki görev. Şimdi ilk seriyi yapalım:
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
İlk kuyruğun seri olarak yürütülmesi gerekmiyor mu? Öyleydi (ve ikincisi de öyleydi). Arka planda başka ne olduysa, kuyruğu hiç ilgilendirmez. Seri kuyruğa seri olarak çalışmasını söyledik ve yaptı ... ama biz ona sadece bir görev verdik. Şimdi ona iki görev verelim:
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
Ve bu, serileştirmenin en temel (ve tek olası) örneğidir - aynı kuyrukta arka planda (ana iş parçacığına) seri olarak (birbiri ardına) çalışan iki görev. Ancak onları iki ayrı seri kuyruk yaparsak (çünkü yukarıdaki örnekte aynı kuyruklar), çıktıları yine karıştırılır:
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
Ve tüm kuyrukların birbirine göre eşzamanlı olduğunu söylediğimde kastettiğim buydu. Bunlar, görevlerini aynı anda yürüten iki seri kuyruktur (çünkü bunlar ayrı kuyruklardır). Bir kuyruk, diğer kuyrukları bilmez veya umursamaz. Şimdi (aynı sıradaki) iki seri sıraya geri dönelim ve eşzamanlı olan üçüncü bir sıra ekleyelim:
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
Bu biraz beklenmedik bir durum, neden eşzamanlı kuyruk, seri kuyrukların yürütülmeden önce bitmesini bekledi? Bu eşzamanlılık değil. Oyun alanınız farklı bir çıktı gösterebilir ama benimki bunu gösterdi. Bunu gösterdi çünkü eşzamanlı sıramın önceliği, GCD'nin görevini daha erken gerçekleştirmesi için yeterince yüksek değildi. Dolayısıyla, her şeyi aynı tutup global kuyruğun QoS'sini değiştirirsem (sadece kuyruğun öncelik seviyesi olan hizmet kalitesi) let concurrentQueue = DispatchQueue.global(qos: .userInteractive)
, çıktı beklendiği gibi olur:
1
1001
1002
1003
2
1004
1005
3
4
5
101
102
103
104
105
İki seri kuyruk, görevlerini seri olarak (beklendiği gibi) yürüttü ve eşzamanlı kuyruk, görevini daha hızlı gerçekleştirdi çünkü ona yüksek bir öncelik seviyesi (yüksek bir QoS veya hizmet kalitesi) verildi.
İlk baskı örneğimizdeki gibi iki eşzamanlı kuyruk, karışık bir çıktı gösteriyor (beklendiği gibi). Düzgün bir şekilde seri olarak yazdırmalarını sağlamak için, her ikisini de aynı seri kuyruğa almamız gerekir (bu kuyruğun aynı örneğini de aynı etiket değil) . Daha sonra her bir görev diğerine göre seri olarak yürütülür. Bununla birlikte, onları seri olarak yazdırmanın başka bir yolu, ikisini de eşzamanlı tutmak ancak gönderim yöntemlerini değiştirmektir:
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
Unutmayın, eşitleme gönderimi yalnızca çağıran iş parçacığının ilerlemeden önce kuyruktaki görev tamamlanana kadar beklemesi anlamına gelir. Buradaki uyarı, açıkçası, arama iş parçacığının ilk görev tamamlanana kadar dondurulmasıdır; bu, kullanıcı arayüzünün gerçekleştirmesini istediğiniz şekilde olabilir veya olmayabilir.
Ve bu nedenle aşağıdakileri yapamayız:
DispatchQueue.main.sync { ... }
Bu, gerçekleştiremediğimiz kuyrukların ve gönderme yöntemlerinin olası tek birleşimidir - ana kuyrukta eşzamanlı gönderim. Bunun nedeni, ana kuyruğun, görevi küme parantezleri içinde yerine getirene kadar dondurulmasını istememizdir. Buna kilitlenme denir. Oyun alanında çalışırken görmek için:
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
Bahsedilecek son bir şey kaynaklardır. Bir kuyruğa görev verdiğimizde, GCD kendi dahili olarak yönetilen havuzunda kullanılabilir bir kuyruk bulur. Bu cevabın yazıldığı kadarıyla, qos başına 64 kuyruk vardır. Bu çok gibi görünebilir, ancak özellikle üçüncü taraf kitaplıklar, özellikle veritabanı çerçeveleri tarafından hızla tüketilebilirler. Bu nedenle, Apple'ın kuyruk yönetimiyle ilgili önerileri vardır (aşağıdaki bağlantılarda belirtilmiştir); biri:
Özel eşzamanlı kuyruklar oluşturmak yerine, görevleri küresel eşzamanlı gönderim kuyruklarından birine gönderin. Seri görevler için, seri kuyruğunuzun hedefini global eşzamanlı kuyruklardan birine ayarlayın.
Bu şekilde, iş parçacıkları oluşturan ayrı kuyrukların sayısını en aza indirirken kuyruğun serileştirilmiş davranışını koruyabilirsiniz.
Bunu yapmak için, daha önce yaptığımız gibi onları oluşturmak yerine (ki hala yapabilirsiniz), Apple aşağıdaki gibi seri kuyruklar oluşturmanızı önerir:
let serialQueue = DispatchQueue(label: "serialQueue", qos: .default, attributes: [], autoreleaseFrequency: .inherit, target: .global(qos: .default))
Daha fazla okumak için aşağıdakileri tavsiye ederim:
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