GCD'de eşzamanlı ve seri sıralar


117

GCD'deki eşzamanlı ve seri kuyrukları tam olarak anlamakta zorlanıyorum. Bazı sorunlarım var ve birinin bana net bir şekilde ve o noktada cevap vermesini umuyorum.

  1. Görevleri birbiri ardına yürütmek için seri kuyrukların oluşturulduğunu ve kullanıldığını okuyorum. Ancak, aşağıdaki durumlarda ne olur:

    • Bir seri kuyruk oluşturuyorum
    • dispatch_asyncÜç kez A, B, C bloğu göndermek için (az önce oluşturduğum seri kuyrukta) kullanıyorum

    Üç blok yürütülecek mi:

    • A, B, C sırasına göre çünkü sıra seri

      VEYA

    • eşzamanlı olarak (aynı zamanda paralel iş parçacıklarında) çünkü ASYNC dağıtımı kullandım
  2. dispatch_syncBlokları birbiri ardına yürütmek için eşzamanlı kuyruklarda kullanabileceğimi okuyorum . Bu durumda, her zaman istediğim kadar SENKRONİZE blok gönderebileceğim eşzamanlı bir kuyruk kullanabildiğim için NEDEN seri kuyruklar var?

    İyi bir açıklama için teşekkürler!


Basit, iyi bir önkoşul soru gönderme senkronizasyonu ve eşzamansız
Honey

Yanıtlar:


216

Basit bir örnek: Yürütmesi bir dakika süren bir bloğunuz var. Ana iş parçacığından bir kuyruğa eklersiniz. Dört vakaya bakalım.

  • zaman uyumsuz - eşzamanlı: kod bir arka plan iş parçacığı üzerinde çalışır. Kontrol hemen ana iş parçacığına (ve UI'ye) döner. Blok, bu kuyrukta çalışan tek blok olduğunu varsayamaz
  • zaman uyumsuz - seri: kod bir arka plan iş parçacığı üzerinde çalışır. Kontrol hemen ana iş parçacığına döner. Blok olabilir o kuyruğa üzerinde çalışan tek blok olduğunu varsayalım
  • sync - eşzamanlı: kod bir arka plan iş parçacığı üzerinde çalışır, ancak ana iş parçacığı, UI'deki herhangi bir güncellemeyi engelleyerek bitmesini bekler. Blok, bu kuyrukta çalışan tek blok olduğunu varsayamaz (birkaç saniye önce asenkron kullanarak başka bir blok ekleyebilirdim)
  • sync - serial: kod bir arka plan iş parçacığı üzerinde çalışır, ancak ana iş parçacığı bunun bitmesini bekler ve UI'deki herhangi bir güncellemeyi engeller. Blok olabilir o kuyruğa üzerinde çalışan tek blok olduğunu varsayalım

Açıkçası, uzun süren işlemler için son ikisini de kullanmazsınız. Normalde, UI'yi başka bir iş parçacığında çalışan bir şeyden (her zaman ana iş parçacığında) güncellemeye çalışırken görürsünüz.


14
Yani bana şunu söylüyorsunuz: (1) kuyruğun türü (kons veya seri), görevlerin sırayla mı yoksa paralel olarak mı yürütüleceğine karar veren TEK öğedir; (2) gönderim türü (senkronizasyon veya asenkron) yalnızca yürütmenin gidip gitmediğini VEYA sonraki talimata gitmediğini mi söylüyor? Demek istediğim, eğer bir görev SYNC gönderirsem, kod hangi kuyrukta çalıştırılırsa çalıştırılsın o görev bitene kadar bloke olur?
Bogdan Alexandru

13
@BogdanAlexandru Doğru. Sıra, bloğu nasıl sıraya koyduğunuzu değil, yürütme ilkesini belirler. Eşitleme, bloğun tamamlanmasını bekler, eşzamansız değil.
Jano

2
@swiftBUTCHER Belli bir noktaya kadar, evet. Bir kuyruk oluşturduğunuzda, maksimum iş parçacığı sayısını belirtebilirsiniz. Bundan daha az görev eklerseniz, bunlar paralel olarak yürütülecektir. Bundan daha fazlasıyla, bazı görevler, kullanılabilir kapasite olana kadar kuyrukta kalacaktır.
Stephen Darlington

2
@PabloA., Ana iş parçacığı bir seri kuyruktur, bu nedenle yalnızca iki durum vardır. Bunun ötesinde, tamamen aynı. Zaman uyumsuz hemen döner (ve blok muhtemelen geçerli çalışma döngüsünün sonunda yürütülür). Eğer senkronizasyon yaparsak ana yakaladım olduğunu gelen ana iş parçacığı için bir kilitlenme almak bu durumda ana iş parçacığı.
Stephen Darlington

1
@ShauketSheikh Hayır. Ana iş parçacığı bir seri kuyruktur, ancak tüm seri kuyruklar ana iş parçacığı değildir. Dördüncü noktada, ana iş parçacığı bloke olur ve başka bir iş parçacığının işiyle rekabet etmesini bekler. Seri kuyruk ana iş parçacığı ise, bir kilitlenme yaşarsınız.
Stephen Darlington

122

İşte bunları anlamamı sağlamak için yaptığım birkaç deney serial, concurrentkuyruklar 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'de eşzamansız kullandığınızda görev farklı iş parçacığında (ana iş parçacığı dışında) çalışır. Async, ana iş parçacığını ve ana kuyruğu engellemeyen, blok yürütülene kadar beklemeyin, sonraki satırı yürüt anlamına gelir. Seri kuyruğundan bu yana, tümü seri kuyruğa eklendikleri sırayla yürütülür.

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'de senkronizasyonu kullandığınızda görev ana ileti dizisinde çalışabilir. Senkronizasyon belirli bir kuyrukta bir blok çalıştırır ve tamamlanmasını bekler ve bu da ana iş parçacığının veya ana sıranın engellenmesine neden olur. Ana sıranın, gönderilen blok tamamlanana kadar beklemesi gerektiğinden, ana iş parçacığı, gönderilen blok tamamlanana kadar Bu nedenle, arka planda çalışan kodun aslında ana iş parçacığı üzerinde yürütülmesi ihtimali vardır Seri kuyruğundan bu yana, tümü eklendikleri sırayla yürütülür (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'de eşzamansız kullandığınızda görev arka planda çalışır. Zaman uyumsuz, sonraki satırı yürüt, blok yürütülene kadar beklemeyin anlamına gelir; Eşzamanlı kuyrukta görevlerin kuyruğa eklendikleri sırayla ancak kuyruğa eklenen farklı iş parçacıklarıyla işlendiğini unutmayın. Sıraya eklendikleri sırada görevi bitirmemeleri gerektiğini unutmayın.Görev sırası, iş parçacıkları zorunlu olarak otomatik olarak oluşturulduğunda farklılık gösterir. Görev paralel olarak yürütülür. Bundan daha fazlasına (maxConcurrentOperationCount) ulaşıldığında, bazı görevler bir iş parçacığı serbest kalana kadar seri olarak davranacaktır.

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'de senkronizasyonu kullandığınızda görev ana ileti dizisinde çalışabilir. Senkronizasyon belirli bir kuyrukta bir blok çalıştırır ve tamamlanmasını bekler ve bu da ana iş parçacığı veya ana sıranın engellenmesine neden olur. Ana sıranın, gönderilen blok tamamlanana kadar beklemesi gerektiğinden, ana iş parçacığı, gönderilen blok tamamlanana kadar beklemesi gerektiğinden, Bu nedenle arka planda çalışan kodun aslında ana iş parçacığı üzerinde yürütülmesi ihtimali vardır. Eşzamanlı kuyruğu olduğundan, görevler kuyruğa eklendikleri sırada tamamlanmayabilir. Ancak senkronize işlemde, farklı evreler tarafından işlenebilse de yapar. Yani, bu seri kuyruk gibi davranır.

İşte bu deneylerin bir özeti

GCD'yi kullanarak yalnızca Sıraya görev eklediğinizi ve bu kuyruktan görev gerçekleştirdiğinizi unutmayın. Sıra, işleminizin zaman uyumlu veya zaman uyumsuz olmasına bağlı olarak görevinizi ana veya arka planda gönderir. Sıra türleri Seri, Eşzamanlı, Ana gönderim kuyruğudur. Gerçekleştirdiğiniz tüm görevler varsayılan olarak Ana gönderim kuyruğundan yapılır.Uygulamanızın kullanması için önceden tanımlanmış dört genel eşzamanlı kuyruk ve bir ana kuyruk vardır (DispatchQueue.main). ayrıca kendi kuyruğunuzu manuel olarak oluşturabilir ve bu kuyruktan görev gerçekleştirebilirsiniz.

Kullanıcı Arabirimi ile ilgili görev, görevi Ana kuyruğa göndererek her zaman ana iş parçacığından gerçekleştirilmelidir.Kısa el yardımcı programı, DispatchQueue.main.sync/asyncağla ilgili / ağır işlemler her zaman eşzamansız olarak yapılmalıdır, ana veya arka planı kullandığınız herhangi bir iş parçacığı yok

DÜZENLEME: Ancak, UI'yi dondurmadan bir arka plan iş parçacığında ağ çağrı işlemlerini eşzamanlı olarak gerçekleştirmeniz gereken durumlar vardır (örneğin, OAuth Belirtecini yenilemek ve başarılı olup olmadığını beklemek). işlemler sırayla ve ana iş parçacığını Engellemeden yürütülür.

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

DÜZENLEME DÜZENLEME: Demo videosunu buradan izleyebilirsiniz


Harika gösteri .... sonraki satır, blok yürütülene kadar beklemeyin, bu da ana iş parçacığını engellemeyen bir sonuç verir , bu nedenle arka planda kesme noktaları kullanırsanız }, o anda gerçekten çalışmadığı için
Honey

@Bu tembel iOS Guy 웃 Eşzamanlı olmayan eşzamanlı ve eşzamansız seri arasındaki farkı hala anlamıyorum. İkisinden birini kullanmanın anlamı nedir? İkisi de arka planda çalışır ve kullanıcı arayüzünü rahatsız etmez. Ve neden hiç senkronizasyon kullanasınız? Tüm kod senkronizasyonu değil. birbiri ardına?
eonist

1
@GitSyncApp videoyu buradan
Anish Parajuli 웃

@ Bu tembel iOS Guy 웃: Bunu yaptığınız için teşekkürler. Slack swift-lang'a yazdım. DispatchGroup ve DispatchWorkItem hakkında da bir tane yapabilirseniz 👌 olurdu. : D
eonist

Sonuncunuzu test ettim concurrentQueue.sync, doLongSyncTaskInConcurrentQueue()fonksiyon, ana iş parçacığını yazdırıyor, Task will run in different threaddoğru görünmüyor.
gabbler

54

Ö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


7

Ben doğru OBEB çalışma şekli hakkında anlamak, iki tür olduğunu düşünüyorum DispatchQueue, serialve concurrentaynı zamanda, nasıl iki yönlü vardır, DispatchQueuegörevlerini, atanan sevk closure, ilki asyncve diğeri sync. Bunlar birlikte, kapatmanın (görevin) gerçekte nasıl yürütüleceğini belirler.

Bunu buldum serialve concurrentkuyruğun kaç tane iş parçacığı kullanabileceğini kastediyorum, bir serialanlamına gelir, oysa concurrentçok anlamına gelir. Ve syncve asyncgörev hangi iş parçacığı üzerinde yürütülecek anlamına, arayanın iş parçacığı veya bu kuyruğu altında yatan iplik, syncoysa araçlar arayanın iş parçacığı üzerinde çalışması asyncanlamına gelir yatan parçacığı üzerinde çalıştırın.

Aşağıdakiler, Xcode oyun alanında çalışabilen deneysel koddur.

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

Umarım yardımcı olabilir.


7

Bu metaforu kullanarak bunu düşünmeyi seviyorum (İşte orijinal görüntünün bağlantısı ):

Babamın biraz yardıma ihtiyacı olacak

Diyelim ki babanız bulaşıkları yıkıyor ve siz bir bardak soda içtiniz. Bardağı temizlemek için babana getir ve diğer tabağın yanına koy.

Şimdi babanız bulaşıkları tek başına yıkıyor, bu yüzden onları tek tek yıkamak zorunda kalacak: Burada babanız bir seri sırayı temsil ediyor .

Ama orada durup temizlenmesini izlemekle gerçekten ilgilenmiyorsun. Yani, bardağı düşürür ve odanıza geri dönersiniz: buna eşzamansız gönderim denir . Babanız işini bitirdikten sonra size haber verebilir veya vermeyebilir, ancak önemli olan camın temizlenmesini beklememenizdir; çocuk işleri için odana geri dönüyorsun.

Şimdi, hala susadığınızı ve aynı bardakta en sevdiğiniz su olmasını istediğinizi ve temizlendiği anda onu gerçekten geri istediğinizi varsayalım. Öyleyse, orada dur ve seninki bitene kadar babanın bulaşıkları yıkışını izle. Bu bir senkronizasyon gönderimidir çünkü görevin tamamlanmasını beklerken bloke olursunuz.

Sonunda, annenizin babanıza yardım etmeye karar verdiğini ve bulaşıkları yıkarken ona katıldığını varsayalım. Artık kuyruk , aynı anda birden fazla bulaşıkları temizleyebildikleri için eşzamanlı bir kuyruğa dönüşür ; ancak yine de orada beklemeye veya nasıl çalıştıklarına bakılmaksızın odanıza dönmeye karar verebileceğinizi unutmayın.

Bu yardımcı olur umarım


3

1. Görevleri birbiri ardına yürütmek için seri kuyrukların oluşturulduğunu ve kullanıldığını okuyorum. Ancak, aşağıdaki durumlarda ne olur: - • Bir seri kuyruk oluşturursam • dispatch_async (yeni oluşturduğum seri kuyrukta) üç kez A, B, C bloğu göndermek için üç kez kullanırsam

CEVAP : - Üç bloğun hepsi birbiri ardına yürütüldü. Anlamaya yardımcı olan bir örnek kod oluşturdum.

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)
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.