Code simultanee e seriali in GCD


117

Sto lottando per comprendere appieno le code simultanee e seriali in GCD. Ho alcuni problemi e spero che qualcuno possa rispondermi chiaramente e sul punto.

  1. Sto leggendo che le code seriali vengono create e utilizzate per eseguire le attività una dopo l'altra. Tuttavia, cosa succede se:

    • Creo una coda seriale
    • Uso dispatch_async(sulla coda seriale che ho appena creato) tre volte per inviare tre blocchi A, B, C

    I tre blocchi verranno eseguiti:

    • nell'ordine A, B, C perché la coda è seriale

      O

    • contemporaneamente (nello stesso tempo su thread paralleli) perché ho usato l'invio ASYNC
  2. Sto leggendo che posso usare dispatch_syncsu code simultanee per eseguire blocchi uno dopo l'altro. In tal caso, PERCHÉ esistono anche code seriali, dal momento che posso sempre utilizzare una coda simultanea in cui posso inviare SINCRONAMENTE tutti i blocchi che voglio?

    Grazie per qualsiasi buona spiegazione!


Una semplice buona domanda prerequisito invia sincronizzazione vs async
Honey

Risposte:


216

Un semplice esempio: hai un blocco che richiede un minuto per essere eseguito. Lo aggiungi a una coda dal thread principale. Diamo un'occhiata ai quattro casi.

  • async - concurrent: il codice viene eseguito su un thread in background. Il controllo torna immediatamente al thread principale (e all'interfaccia utente). Il blocco non può presumere che sia l'unico blocco in esecuzione su quella coda
  • async - seriale: il codice viene eseguito su un thread in background. Il controllo ritorna immediatamente al thread principale. Il blocco può presumere che sia l'unico blocco in esecuzione su quella coda
  • sincronizzazione - simultanea: il codice viene eseguito su un thread in background ma il thread principale attende che finisca, bloccando qualsiasi aggiornamento all'interfaccia utente. Il blocco non può presumere che sia l'unico blocco in esecuzione su quella coda (avrei potuto aggiungere un altro blocco utilizzando async pochi secondi prima)
  • sincronizzazione - seriale: il codice viene eseguito su un thread in background ma il thread principale attende che finisca, bloccando eventuali aggiornamenti dell'interfaccia utente. Il blocco può presumere che sia l'unico blocco in esecuzione su quella coda

Ovviamente non useresti nessuno degli ultimi due per processi a lunga esecuzione. Normalmente lo vedi quando provi ad aggiornare l'interfaccia utente (sempre sul thread principale) da qualcosa che potrebbe essere in esecuzione su un altro thread.


14
Quindi mi stai dicendo che: (1) il tipo di coda (conc o seriale) è l'UNICO elemento che decide se le attività vengono eseguite in ordine o in parallelo ;; (2) il tipo di invio (sync o async) sta solo dicendo se l'esecuzione va OPPURE non va all'istruzione successiva? Voglio dire, se invio un'attività SYNC, il codice si bloccherà fino al termine dell'attività, indipendentemente dalla coda su cui viene eseguita?
Bogdan Alexandru

13
@BogdanAlexandru Correct. La coda determina la politica di esecuzione, non il modo in cui accodare il blocco. La sincronizzazione attende il completamento del blocco, asincrona no.
Jano

2
@swiftBUTCHER Fino a un certo punto, sì. Quando crei una coda puoi specificare il numero massimo di thread. Se aggiungi meno attività di quelle, verranno eseguite in parallelo. Con più di questo, alcune attività rimarranno in coda fino a quando non sarà disponibile capacità.
Stephen Darlington

2
@ PabloA., Il thread principale è una coda seriale, quindi ci sono solo due casi. Oltre a ciò, è esattamente lo stesso. Async ritorna immediatamente (e il blocco probabilmente viene eseguito alla fine del ciclo di esecuzione corrente). Il problema principale è che se esegui la sincronizzazione dal thread principale al thread principale, nel qual caso ottieni un deadlock.
Stephen Darlington

1
@ShauketSheikh No. Il thread principale è una coda seriale, ma non tutte le code seriali sono il thread principale. Nel quarto punto, il thread principale si bloccherebbe, in attesa che un altro thread completi il ​​suo lavoro. Se la coda seriale fosse il thread principale, avresti un deadlock.
Stephen Darlington

122

Qui ci sono un paio di esperimenti che ho fatto per farmi capire su questi serial, concurrentcode con 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")
        }
    }
}

L'attività verrà eseguita in un thread diverso (diverso dal thread principale) quando si utilizza async in GCD. Async significa eseguire la riga successiva, non attendere fino a quando il blocco non viene eseguito, il che risulta non bloccando il thread principale e la coda principale. Dal momento che la sua coda seriale, tutte vengono eseguite nell'ordine in cui vengono aggiunte alla coda seriale. Le attività eseguite in serie sono sempre eseguite una alla volta dal singolo thread associato alla coda.

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

L'attività può essere eseguita nel thread principale quando si utilizza la sincronizzazione in GCD. La sincronizzazione esegue un blocco su una data coda e attende il completamento, il che si traduce nel blocco del thread principale o della coda principale. Poiché la coda principale deve attendere fino al completamento del blocco inviato, il thread principale sarà disponibile per elaborare i blocchi da code diverse da coda principale, quindi c'è la possibilità che il codice in esecuzione sulla coda in background possa essere effettivamente in esecuzione sul thread principale. Dal momento che la sua coda seriale, tutti vengono eseguiti nell'ordine in cui vengono aggiunti (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")
    }
}

L'attività verrà eseguita nel thread in background quando si utilizza async in GCD. Async significa eseguire la riga successiva, non attendere fino a quando il blocco non viene eseguito, il che risulta non bloccando il thread principale. Ricorda che nella coda simultanea, le attività vengono elaborate nell'ordine in cui vengono aggiunte alla coda, ma con thread diversi collegati alla coda. Ricorda che non dovrebbero finire l'attività poiché l'ordine in cui vengono aggiunti alla coda. L'ordine dell'attività è diverso ogni volta che i thread vengono creati in modo necessariamente automatico. Le attività vengono eseguite in parallelo. Con più di questo (maxConcurrentOperationCount) viene raggiunto, alcune attività si comporteranno come una seriale fino a quando un thread non sarà libero.

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

L'attività può essere eseguita nel thread principale quando si utilizza la sincronizzazione in GCD. La sincronizzazione esegue un blocco su una data coda e attende il completamento, il che si traduce nel blocco del thread principale o della coda principale. Poiché la coda principale deve attendere fino al completamento del blocco inviato, il thread principale sarà disponibile per elaborare i blocchi da code diverse da coda principale, quindi esiste la possibilità che il codice in esecuzione sulla coda in background sia effettivamente in esecuzione sul thread principale. Poiché è in coda simultanea, le attività potrebbero non finire nell'ordine in cui sono state aggiunte alla coda. Ma con l'operazione sincrona lo fa sebbene possano essere elaborati da thread diversi. Quindi, si comporta come questa è la coda seriale.

Ecco un riassunto di questi esperimenti

Ricorda che usando GCD stai solo aggiungendo attività alla coda ed eseguendo attività da quella coda. Queue invia l'attività nel thread principale o in background a seconda che l'operazione sia sincrona o asincrona. I tipi di code sono Seriale, Concorrente, Coda di invio principale Tutte le attività che esegui vengono eseguite per impostazione predefinita dalla Coda di invio principale Ci sono già quattro code simultanee globali predefinite per l'applicazione da utilizzare e una coda principale (DispatchQueue.main). puoi anche creare manualmente la tua coda ed eseguire attività da quella coda.

L'attività correlata all'interfaccia utente deve essere sempre eseguita dal thread principale inviando l'attività alla coda DispatchQueue.main.sync/asyncprincipale.

EDIT: Tuttavia, ci sono casi in cui è necessario eseguire operazioni di chiamate di rete in modo sincrono in un thread in background senza bloccare l'interfaccia utente (ad esempio aggiornare il token OAuth e attendere se ha successo o meno) .È necessario avvolgere quel metodo all'interno di un'operazione asincrona. le operazioni vengono eseguite nell'ordine e senza bloccare il thread principale.

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: Puoi guardare il video dimostrativo qui


Grande dimostrazione .... la riga successiva non aspettare fino a quando il blocco non viene eseguito, il che non blocca il thread principale, ecco perché se usi i punti di interruzione su un thread in background, salterà al }perché in quel momento non è in esecuzione
Honey

@ Quel pigro ragazzo iOS 웃 Non capisco ancora la differenza tra asincrono simultaneo e seriale asincrono. Qual è l'implicazione dell'utilizzo di entrambi. Entrambi vengono eseguiti in background senza disturbare l'interfaccia utente. E perché mai dovresti usare la sincronizzazione? Non è tutto sincronizzato con il codice. uno dopo l'altro?
eonista

1
@GitSyncApp puoi guardare il video qui
Anish Parajuli 웃

@ Quel pigro iOS Guy 웃: grazie per averlo fatto. Ho pubblicato un post su Slack Swift-lang. Sarebbe 👌 Se potessi crearne uno anche su DispatchGroup e DispatchWorkItem. : D
eonista

Ho testato il tuo ultimo, la funzione concurrentQueue.syncof doLongSyncTaskInConcurrentQueue(), stampa il thread principale, Task will run in different threadnon sembra vero.
gabbler

54

Innanzitutto, è importante conoscere la differenza tra thread e code e cosa fa realmente GCD. Quando usiamo le code di invio (tramite GCD), stiamo davvero facendo la coda, non il threading. Il framework Dispatch è stato progettato specificamente per allontanarci dal threading, poiché Apple ammette che "implementare una soluzione di threading corretta [può] diventare estremamente difficile, se non [a volte] impossibile da ottenere". Pertanto, per eseguire attività contemporaneamente (attività che non vogliamo congelare l'interfaccia utente), tutto ciò che dobbiamo fare è creare una coda di tali attività e passarla a GCD. E GCD gestisce tutti i thread associati. Pertanto, tutto ciò che stiamo facendo è fare la fila.

La seconda cosa da sapere subito è cos'è un compito. Un'attività è tutto il codice all'interno di quel blocco di coda (non all'interno della coda, perché possiamo aggiungere cose a una coda tutto il tempo, ma entro la chiusura in cui lo abbiamo aggiunto alla coda). A volte ci si riferisce a un'attività come a un blocco e a un blocco a volte come a un'attività (ma sono più comunemente note come attività, in particolare nella comunità Swift). E non importa quanto codice o poco codice, tutto il codice all'interno delle parentesi graffe è considerato una singola attività:

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
}

Ed è ovvio menzionare che simultaneo significa semplicemente allo stesso tempo con altre cose e significa seriale uno dopo l'altro (mai allo stesso tempo). Serializzare qualcosa, o mettere qualcosa in seriale, significa semplicemente eseguirlo dall'inizio alla fine nel suo ordine da sinistra a destra, dall'alto verso il basso, senza interruzioni.

Esistono due tipi di code, seriale e simultanea, ma tutte le code sono simultanee l'una rispetto all'altra . Il fatto che si desideri eseguire qualsiasi codice "in background" significa che si desidera eseguirlo contemporaneamente a un altro thread (di solito il thread principale). Pertanto, tutte le code di invio, seriali o simultanee, eseguono le loro attività contemporaneamente rispetto ad altre code . Qualsiasi serializzazione eseguita da code (da code seriali), ha solo a che fare con le attività all'interno di quella singola coda di invio [seriale] (come nell'esempio sopra dove ci sono due attività all'interno della stessa coda seriale; quelle attività verranno eseguite una dopo l'altro, mai contemporaneamente).

CODE SERIALI (spesso note come code di invio private) garantiscono l'esecuzione delle attività una alla volta dall'inizio alla fine nell'ordine in cui sono state aggiunte a quella coda specifica. Questa è l'unica garanzia di serializzazione ovunque nella discussione sulle code di invio--che le attività specifiche all'interno di una coda seriale specifica vengono eseguite in serie. Le code seriali possono, tuttavia, essere eseguite simultaneamente con altre code seriali se sono code separate perché, ancora una volta, tutte le code sono simultanee l'una rispetto all'altra. Tutte le attività vengono eseguite su thread distinti, ma non è garantito che tutte le attività vengano eseguite sullo stesso thread (non importante, ma interessante da sapere). E il framework iOS non viene fornito con code seriali pronte per l'uso, devi crearle. Le code private (non globali) sono seriali per impostazione predefinita, quindi per creare una coda seriale:

let serialQueue = DispatchQueue(label: "serial")

Puoi renderlo concorrente attraverso la sua proprietà attributo:

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

Ma a questo punto, se non stai aggiungendo altri attributi alla coda privata, Apple consiglia di utilizzare solo una delle loro code globali pronte all'uso (che sono tutte simultanee). In fondo a questa risposta, vedrai un altro modo per creare code seriali (utilizzando la proprietà target), che è il modo in cui Apple consiglia di farlo (per una gestione delle risorse più efficiente). Ma per ora, etichettarlo è sufficiente.

CODE CONCORRENTI (spesso note come code di invio globali) possono eseguire attività contemporaneamente; tuttavia, è garantito che le attività vengano avviate nell'ordine in cui sono state aggiunte a quella coda specifica, ma a differenza delle code seriali, la coda non attende il completamento della prima attività prima di avviare la seconda. Le attività (come con le code seriali) vengono eseguite su thread distinti e (come con le code seriali) non è garantito che tutte le attività vengano eseguite sullo stesso thread (non importante, ma interessante da sapere). E il framework iOS viene fornito con quattro code simultanee pronte per l'uso. Puoi creare una coda simultanea usando l'esempio sopra o usando una delle code globali di Apple (che di solito è consigliata):

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

RESISTENTE AL CICLO DI RETAIN: le code di invio sono oggetti conteggiati in riferimento ma non è necessario conservare e rilasciare le code globali perché sono globali, quindi la conservazione e il rilascio vengono ignorati. È possibile accedere direttamente alle code globali senza doverle assegnare a una proprietà.

Esistono due modi per inviare le code: in modo sincrono e asincrono.

SYNC DISPATCHING significa che il thread in cui è stata inviata la coda (il thread chiamante) si interrompe dopo l'invio della coda e attende che l'attività in quel blocco di coda termini l'esecuzione prima di riprendere. Per inviare in modo sincrono:

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

ASYNC DISPATCHING significa che il thread chiamante continua a essere eseguito dopo l'invio della coda e non attende che l'attività in quel blocco di coda termini l'esecuzione. Per inviare in modo asincrono:

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

Ora si potrebbe pensare che per eseguire un'attività in seriale, dovrebbe essere utilizzata una coda seriale, e questo non è esattamente corretto. Per eseguire più attività in serie, è necessario utilizzare una coda seriale, ma tutte le attività (isolate da sole) vengono eseguite in serie. Considera questo esempio:

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

Indipendentemente dal modo in cui configuri (seriale o simultaneo) o invii (sincronizza o asincrono) questa coda, questa attività verrà sempre eseguita in serie. Il terzo ciclo non verrà mai eseguito prima del secondo ciclo e il secondo ciclo non verrà mai eseguito prima del primo ciclo. Questo è vero in qualsiasi coda utilizzando qualsiasi invio. È quando si introducono più attività e / o code in cui la seriale e la concorrenza entrano davvero in gioco.

Considera queste due code, una seriale e una simultanea:

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

Supponiamo di inviare due code simultanee in modo asincrono:

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

Il loro output è confuso (come previsto) ma si noti che ogni coda ha eseguito la propria attività in serie. Questo è l'esempio più semplice di concorrenza: due attività in esecuzione contemporaneamente in background nella stessa coda. Ora creiamo il primo seriale:

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

La prima coda non dovrebbe essere eseguita in serie? Lo era (e così è stato il secondo). Qualunque altra cosa sia accaduta in background non riguarda la coda. Abbiamo detto alla coda seriale di eseguire in seriale e lo ha fatto ... ma gli abbiamo assegnato solo un compito. Ora diamo due compiti:

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

E questo è l'esempio più semplice (e l'unico possibile) di serializzazione: due attività in esecuzione in serie (una dopo l'altra) in background (sul thread principale) nella stessa coda. Ma se li abbiamo creati due code seriali separate (perché nell'esempio sopra sono la stessa coda), il loro output è di nuovo confuso:

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

E questo è ciò che intendevo quando ho detto che tutte le code sono simultanee l'una rispetto all'altra. Si tratta di due code seriali che eseguono le loro attività contemporaneamente (perché sono code separate). Una coda non conosce o non si preoccupa delle altre code. Ora torniamo a due code seriali (della stessa coda) e aggiungiamo una terza coda, simultanea:

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

È un po 'inaspettato, perché la coda simultanea ha atteso il termine delle code seriali prima di essere eseguita? Questa non è concorrenza. Il tuo parco giochi potrebbe mostrare un output diverso ma il mio lo ha mostrato. E lo ha dimostrato perché la priorità della mia coda simultanea non era abbastanza alta da consentire a GCD di eseguire il suo compito prima. Quindi, se mantengo tutto uguale ma cambio il QoS della coda globale (la sua qualità del servizio, che è semplicemente il livello di priorità della coda) let concurrentQueue = DispatchQueue.global(qos: .userInteractive), l'output è come previsto:

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

Le due code seriali hanno eseguito le loro attività in serie (come previsto) e la coda simultanea ha eseguito la sua attività più rapidamente perché le è stato assegnato un livello di priorità elevato (un alto QoS o qualità del servizio).

Due code simultanee, come nel nostro primo esempio di stampa, mostrano una stampa confusa (come previsto). Per farli stampare ordinatamente in serie, dovremmo renderli entrambi la stessa coda seriale (la stessa istanza di quella coda, e non solo la stessa etichetta) . Quindi ogni attività viene eseguita in serie rispetto all'altra. Un altro modo, tuttavia, per farli stampare in serie è mantenerli entrambi simultanei ma cambiare il loro metodo di spedizione:

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

Ricorda, l'invio di sincronizzazione significa solo che il thread chiamante attende fino al completamento dell'attività nella coda prima di procedere. L'avvertenza qui, ovviamente, è che il thread chiamante viene bloccato fino al completamento della prima attività, il che può essere o meno il modo in cui desideri che l'interfaccia utente venga eseguita.

Ed è per questo motivo che non possiamo fare quanto segue:

DispatchQueue.main.sync { ... }

Questa è l'unica combinazione possibile di code e metodi di invio che non possiamo eseguire: invio sincrono sulla coda principale. E questo perché stiamo chiedendo alla coda principale di bloccarsi fino a quando non eseguiamo l'attività all'interno delle parentesi graffe ... che abbiamo inviato alla coda principale, che abbiamo appena congelato. Questo si chiama deadlock. Per vederlo in azione in un parco giochi:

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

Un'ultima cosa da menzionare sono le risorse. Quando assegniamo un'attività a una coda, GCD trova una coda disponibile dal pool gestito internamente. Per quanto riguarda la scrittura di questa risposta, ci sono 64 code disponibili per qos. Può sembrare molto, ma possono essere consumati rapidamente, specialmente da librerie di terze parti, in particolare framework di database. Per questo motivo, Apple offre consigli sulla gestione delle code (menzionati nei collegamenti sottostanti); un essere:

Invece di creare code simultanee private, inviare le attività a una delle code di invio simultanee globali. Per le attività seriali, impostare la destinazione della coda seriale su una delle code simultanee globali. In questo modo, puoi mantenere il comportamento serializzato della coda riducendo al minimo il numero di code separate che creano thread.

Per fare ciò, invece di crearli come abbiamo fatto prima (cosa che puoi ancora fare), Apple consiglia di creare code seriali come questa:

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

Per ulteriori letture, consiglio quanto segue:

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

Se ho capito bene come funziona GCD, penso che ci sono due tipi di DispatchQueue, seriale concurrent, allo stesso tempo, ci sono due modi come DispatchQueuele spedizioni dei propri compiti, il assegnato closure, primo è async, e l'altro è sync. Quelli insieme determinano come la chiusura (attività) viene effettivamente eseguita.

Ho scoperto che seriale concurrentintendo quanti thread può usare quella coda, serialsignifica uno, mentre concurrentsignifica molti. E synce asyncsignifica che l'operazione è eseguita sul quale filo, filo del chiamante o il filo sottostante quella coda, syncmezzi eseguiti sul filo del chiamante che asyncmezzi eseguiti sul filo conduttore.

Quello che segue è un codice sperimentale che può essere eseguito nel playground 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()

Spero possa essere utile.


7

Mi piace pensarlo usando questa metafora (ecco il link all'immagine originale):

Papà avrà bisogno di aiuto

Immaginiamo che tuo padre stia lavando i piatti e tu abbia appena bevuto un bicchiere di soda. Tu porti il ​​bicchiere a tuo padre per pulirlo, mettendolo accanto all'altro piatto.

Ora tuo padre sta lavando i piatti da solo, quindi dovrà lavarli uno per uno: tuo padre qui rappresenta una coda seriale .

Ma non sei davvero interessato a stare lì a guardare mentre viene ripulito. Quindi, lasci cadere il bicchiere e torni nella tua stanza: questo è chiamato invio asincrono . Tuo padre potrebbe o meno farti sapere una volta che ha finito, ma la cosa importante è che non stai aspettando che il vetro venga pulito; torni nella tua stanza per fare, sai, cose da bambini.

Ora supponiamo che tu abbia ancora sete e desideri avere dell'acqua sullo stesso bicchiere che sembra essere il tuo preferito, e lo rivuoi davvero indietro non appena viene ripulito. Quindi, stai lì e guardi tuo padre che lava i piatti finché il tuo non ha finito. Questo è un invio di sincronizzazione , poiché sei bloccato mentre aspetti che l'attività venga completata.

E infine diciamo che tua madre decide di aiutare tuo padre e si unisce a lui a lavare i piatti. Ora la coda diventa una coda simultanea poiché possono pulire più piatti contemporaneamente; ma nota che puoi ancora decidere di aspettare lì o tornare nella tua stanza, indipendentemente da come funzionano.

Spero che questo ti aiuti


3

1. Sto leggendo che le code seriali vengono create e utilizzate per eseguire le attività una dopo l'altra. Tuttavia, cosa succede se: - • Creo una coda seriale • Uso tre volte dispatch_async (sulla coda seriale che ho creato) per inviare tre blocchi A, B, C

RISPOSTA : - Tutti e tre i blocchi eseguiti uno dopo l'altro. Ho creato un codice di esempio che aiuta a capire.

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)
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.