Filas simultâneas ou seriais no GCD

117

Estou lutando para entender totalmente as filas simultâneas e seriais no GCD. Tenho alguns problemas e espero que alguém possa me responder com clareza e na hora.

  1. Estou lendo que filas seriais são criadas e usadas para executar tarefas uma após a outra. No entanto, o que acontece se:

    • Eu crio uma fila serial
    • Eu uso dispatch_async(na fila serial que acabei de criar) três vezes para despachar três blocos A, B, C

    Os três blocos serão executados:

    • na ordem A, B, C porque a fila é serial

      OU

    • simultaneamente (ao mesmo tempo em threads paralelas) porque usei o envio ASYNC
  2. Estou lendo que posso usar dispatch_syncem filas simultâneas para executar blocos um após o outro. Nesse caso, POR QUE as filas seriais existem, já que sempre posso usar uma fila simultânea onde posso enviar SINCRONAMENTE quantos blocos eu quiser?

    Obrigado por qualquer boa explicação!

Bogdan Alexandru
fonte
Uma pergunta de pré-requisito simples, enviar sincronização vs assíncrona
Honey,

Respostas:

216

Um exemplo simples: você tem um bloco que leva um minuto para ser executado. Você o adiciona a uma fila do thread principal. Vejamos os quatro casos.

  • assíncrono - simultâneo: o código é executado em um thread de segundo plano. O controle retorna imediatamente para o thread principal (e UI). O bloco não pode assumir que é o único bloco em execução na fila
  • async - serial: o código é executado em um thread de segundo plano. O controle retorna imediatamente ao thread principal. O bloco pode assumir que é o único bloco em execução na fila
  • sync - concorrente: o código é executado em um thread em segundo plano, mas o thread principal espera que ele termine, bloqueando todas as atualizações na IU. O bloco não pode assumir que é o único bloco em execução na fila (eu poderia ter adicionado outro bloco usando assíncrono alguns segundos antes)
  • sync - serial: o código é executado em um thread em segundo plano, mas o thread principal espera que ele termine, bloqueando qualquer atualização na IU. O bloco pode assumir que é o único bloco em execução na fila

Obviamente, você não usaria nenhum dos dois últimos para processos de longa execução. Você normalmente o vê quando está tentando atualizar a IU (sempre no thread principal) de algo que pode estar sendo executado em outro thread.

Stephen Darlington
fonte
14
Então você está me dizendo que: (1) o tipo da fila (conc ou serial) é o ÚNICO elemento que decide se as tarefas são executadas em ordem ou em paralelo ;; (2) o tipo de despacho (sinc ou assíncrono) só diz se a execução vai OU não vai para a próxima instrução? Quer dizer, se eu despachar uma tarefa SYNC, o código será bloqueado até que a tarefa seja concluída, não importa em qual fila ela seja executada?
Bogdan Alexandru
13
@BogdanAlexandru Correto. A fila dita a política de execução, não como você enfileira o bloco. A sincronização aguarda a conclusão do bloqueio, não o assíncrono
Jano,
2
@swiftBUTCHER Até certo ponto, sim. Ao criar uma fila, você pode especificar o número máximo de threads. Se você adicionar menos tarefas do que isso, elas serão executadas em paralelo. Com mais do que isso, algumas tarefas permanecerão em uma fila até que haja capacidade disponível.
Stephen Darlington
2
@PabloA., O thread principal é uma fila serial, portanto, existem apenas dois casos. Além disso, é exatamente o mesmo. Async retorna imediatamente (e o bloco provavelmente é executado no final do loop de execução atual). O principal problema é se você sincronizar do thread principal para o thread principal, caso em que você obterá um deadlock.
Stephen Darlington
1
@ShauketSheikh Não. O thread principal é uma fila serial, mas nem todas as filas seriais são o thread principal. No quarto ponto, o thread principal seria bloqueado, aguardando outro thread para competir com seu trabalho. Se a fila serial fosse o thread principal, você teria um deadlock.
Stephen Darlington
122

Aqui estão algumas das experiências que eu tenho feito para me fazer entender sobre estes serial, concurrentfilas com 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")
        }
    }
}

A tarefa será executada em um thread diferente (diferente do thread principal) quando você usa assíncrono no GCD. Assíncrono significa executar a próxima linha, não esperar até que o bloco seja executado, o que resulta no não bloqueio do thread principal e da fila principal. Desde sua fila serial, todas são executadas na ordem em que são adicionadas à fila serial. As tarefas executadas em série são sempre executadas uma de cada vez pelo único thread associado à Fila.

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

A tarefa pode ser executada no thread principal quando você usa a sincronização no GCD. A sincronização executa um bloco em uma determinada fila e espera sua conclusão, o que resulta no bloqueio do thread principal ou da fila principal. Visto que a fila principal precisa esperar até que o bloco despachado seja concluído, o thread principal estará disponível para processar blocos de filas diferentes de fila principal. Portanto, há uma chance de que o código em execução na fila de segundo plano possa, na verdade, estar em execução na thread principal. Como sua fila serial, todos são executados na ordem em que são adicionados (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")
    }
}

A tarefa será executada no thread de segundo plano quando você usar assíncrono no GCD. Assíncrono significa executar a próxima linha, não esperar até que o bloco seja executado, o que resulta em thread principal sem bloqueio. Lembre-se de que, na fila simultânea, as tarefas são processadas na ordem em que são adicionadas à fila, mas com diferentes threads anexados à fila. Lembre-se de que eles não devem terminar a tarefa, pois a ordem em que são adicionados à fila. A ordem da tarefa difere cada vez que os threads são criados, necessariamente de forma automática. As tarefas são executadas em paralelo. Com mais do que (maxConcurrentOperationCount) é alcançado, algumas tarefas se comportarão como uma série até que um thread esteja livre.

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

A tarefa pode ser executada no thread principal quando você usa a sincronização no GCD. A sincronização executa um bloco em uma determinada fila e espera sua conclusão, o que resulta no bloqueio do thread principal ou da fila principal. Visto que a fila principal precisa esperar até que o bloco despachado seja concluído, o thread principal estará disponível para processar blocos de filas diferentes de fila principal. Portanto, há uma chance de o código em execução na fila de segundo plano estar realmente sendo executado no thread principal. Por causa de sua fila simultânea, as tarefas podem não terminar na ordem em que são adicionadas à fila. Mas com a operação síncrona ele faz, embora possam ser processados ​​por diferentes threads. Então, ele se comporta como se fosse uma fila serial.

Aqui está um resumo dessas experiências

Lembre-se de usar o GCD, você está apenas adicionando tarefas à fila e realizando tarefas a partir dessa fila. O Queue despacha sua tarefa no thread principal ou em segundo plano, dependendo se a operação é síncrona ou assíncrona. Os tipos de filas são Serial, Concorrente, Fila de envio principal. Todas as tarefas que você executa são feitas por padrão na fila de envio principal. Já existem quatro filas globais simultâneas predefinidas para seu aplicativo usar e uma fila principal (DispatchQueue.main). Você também pode criar manualmente sua própria fila e executar tarefas a partir dessa fila.

A tarefa relacionada à interface do usuário deve sempre ser realizada a partir do thread principal, despachando a tarefa para a fila principal. O utilitário de mão curta é, DispatchQueue.main.sync/asyncenquanto as operações relacionadas / pesadas da rede devem sempre ser feitas de forma assíncrona, independentemente do thread que você está usando, principal ou de fundo

EDITAR: No entanto, há casos em que você precisa realizar operações de chamadas de rede de forma síncrona em um thread de segundo plano sem congelar a IU (por exemplo, refazer o token OAuth e esperar se tiver sucesso ou não). Você precisa envolver esse método dentro de uma operação assíncrona. as operações são executadas na ordem e sem o thread principal de bloqueio.

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

EDITAR EDITAR: Você pode assistir ao vídeo de demonstração aqui

LC 웃
fonte
Grande demonstração ... próxima linha não espere até que o bloco seja executado, o que resulta em um thread principal sem bloqueio, por isso, se você usar pontos de interrupção em um thread de segundo plano, ele irá pular para o }porque ele realmente não está executando naquele momento
Honey
@ Aquele cara do iOS preguiçoso 웃 Eu ainda não entendo a diferença entre simultâneo assíncrono e serial assíncrono. Qual é a implicação de usar qualquer um deles. Ambos são executados em segundo plano, não perturbando a IU. E por que você usaria a sincronização? Nem tudo é sincronização de código. um após o outro?
eonist
1
@GitSyncApp você pode assistir ao vídeo aqui
Anish Parajuli 웃
@ Aquele cara do iOS preguiçoso 웃: obrigado por fazer isso. Postei no slack swift-lang. Seria 👌 Se você pudesse fazer um sobre DispatchGroup e DispatchWorkItem também. : D
eonista
Testei o seu último, a função concurrentQueue.syncof doLongSyncTaskInConcurrentQueue(), ele imprime o thread principal, Task will run in different threadparece não ser verdade.
gabbler
54

Primeiro, é importante saber a diferença entre threads e filas e o que o GCD realmente faz. Quando usamos filas de despacho (por meio de GCD), estamos realmente enfileirando, não encadeando. A estrutura do Dispatch foi projetada especificamente para nos afastar do threading, já que a Apple admite que "implementar uma solução de threading correta [pode] se tornar extremamente difícil, se não [às vezes] impossível de alcançar." Portanto, para realizar tarefas simultaneamente (tarefas que não queremos travar a IU), tudo o que precisamos fazer é criar uma fila dessas tarefas e entregá-la ao GCD. E o GCD trata de todo o threading associado. Portanto, tudo o que realmente estamos fazendo é uma fila.

A segunda coisa a saber imediatamente é o que é uma tarefa. Uma tarefa é todo o código dentro desse bloco de fila (não dentro da fila, porque podemos adicionar coisas a uma fila o tempo todo, mas dentro do fechamento onde adicionamos à fila). Uma tarefa às vezes é referida como um bloco e um bloco às vezes é referido como uma tarefa (mas eles são mais comumente conhecidos como tarefas, particularmente na comunidade Swift). E não importa quanto código ou pouco, todos os códigos entre chaves são considerados uma única tarefa:

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
}

E é óbvio mencionar que simultâneo simplesmente significa ao mesmo tempo com outras coisas e serial significa um após o outro (nunca ao mesmo tempo). Serializar algo, ou colocar algo em serial, significa apenas executá-lo do início ao fim na ordem da esquerda para a direita, de cima para baixo, sem interrupções.

Existem dois tipos de filas, seriais e simultâneas, mas todas as filas são simultâneas entre si . O fato de você querer executar qualquer código "em segundo plano" significa que você deseja executá-lo simultaneamente com outro encadeamento (geralmente o encadeamento principal). Portanto, todas as filas de despacho, seriais ou simultâneas, executam suas tarefas simultaneamente em relação a outras filas . Qualquer serialização realizada por filas (por filas seriais), tem a ver apenas com as tarefas dentro dessa única fila de despacho [serial] (como no exemplo acima, onde há duas tarefas dentro da mesma fila serial; essas tarefas serão executadas uma após o outro, nunca simultaneamente).

AS SERIAL QUEUES (também conhecidas como filas de despacho privadas) garantem a execução de tarefas uma por vez, do início ao fim, na ordem em que foram adicionadas a essa fila específica. Esta é a única garantia de serialização em qualquer lugar na discussão de filas de despacho--que as tarefas específicas dentro de uma fila serial específica são executadas em série. No entanto, as filas seriais podem ser executadas simultaneamente com outras filas seriais se forem filas separadas porque, novamente, todas as filas são concorrentes umas em relação às outras. Todas as tarefas são executadas em threads distintos, mas nem todas as tarefas são executadas no mesmo thread (não é importante, mas é interessante saber). E a estrutura do iOS não vem com nenhuma fila serial pronta para usar, você deve criá-la. As filas privadas (não globais) são seriais por padrão, portanto, para criar uma fila serial:

let serialQueue = DispatchQueue(label: "serial")

Você pode torná-lo simultâneo por meio de sua propriedade de atributo:

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

Mas, neste ponto, se você não estiver adicionando nenhum outro atributo à fila privada, a Apple recomenda que você use apenas uma de suas filas globais prontas para uso (que são todas simultâneas). No final desta resposta, você verá outra maneira de criar filas seriais (usando a propriedade target), que é como a Apple recomenda fazer (para um gerenciamento de recursos mais eficiente). Mas, por enquanto, rotulá-lo é suficiente.

CONCURRENT QUEUES (frequentemente conhecidas como filas de despacho global) podem executar tarefas simultaneamente; as tarefas, no entanto, têm a garantia de iniciar na ordem em que foram adicionadas a essa fila específica, mas, ao contrário das filas seriais, a fila não espera que a primeira tarefa termine antes de iniciar a segunda tarefa. Tarefas (como com filas seriais) são executadas em threads distintos e (como com filas seriais) nem todas as tarefas têm garantia de execução no mesmo thread (não é importante, mas é interessante saber). E a estrutura do iOS vem com quatro filas simultâneas prontas para usar. Você pode criar uma fila simultânea usando o exemplo acima ou usando uma das filas globais da Apple (o que geralmente é recomendado):

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

RETAIN-CYCLE RESISTANT: As filas de despacho são objetos contados por referência, mas você não precisa reter e liberar filas globais porque são globais e, portanto, a retenção e a liberação são ignoradas. Você pode acessar as filas globais diretamente, sem precisar atribuí-las a uma propriedade.

Existem duas maneiras de despachar filas: de forma síncrona e assíncrona.

SYNC DISPATCHING significa que o encadeamento onde a fila foi despachada (o encadeamento de chamada) pausa após despachar a fila e espera que a tarefa nesse bloco de fila termine de ser executada antes de continuar. Para despachar de forma síncrona:

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

ASYNC DISPATCHING significa que o thread de chamada continua a ser executado após o despacho da fila e não espera que a tarefa nesse bloco de fila termine de ser executada. Para despachar de forma assíncrona:

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

Agora, pode-se pensar que, para executar uma tarefa em série, uma fila serial deve ser usada, o que não é exatamente correto. Para executar várias tarefas em série, deve-se usar uma fila serial, mas todas as tarefas (isoladas por si mesmas) são executadas em série. Considere este exemplo:

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

Não importa como você configura (serial ou simultâneo) ou despacha (sincroniza ou assíncrona) esta fila, esta tarefa sempre será executada em serial. O terceiro loop nunca será executado antes do segundo loop e o segundo loop nunca será executado antes do primeiro. Isso é verdade em qualquer fila usando qualquer despacho. É quando você introduz várias tarefas e / ou filas que a série e a simultaneidade realmente entram em jogo.

Considere essas duas filas, uma serial e outra simultânea:

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

Digamos que despachamos duas filas simultâneas em assíncrono:

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

Sua saída é confusa (como esperado), mas observe que cada fila executou sua própria tarefa em série. Este é o exemplo mais básico de simultaneidade - duas tarefas em execução ao mesmo tempo em segundo plano na mesma fila. Agora vamos fazer o primeiro serial:

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

A primeira fila não deveria ser executada em série? Foi (e assim foi o segundo). O que quer que tenha acontecido em segundo plano não é motivo de preocupação para a fila. Dissemos à fila serial para executar em série e assim fez ... mas demos apenas uma tarefa. Agora vamos dar duas tarefas:

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 este é o exemplo mais básico (e apenas possível) de serialização - duas tarefas em execução em série (uma após a outra) em segundo plano (para o thread principal) na mesma fila. Mas se fizermos delas duas filas seriais separadas (porque no exemplo acima elas são a mesma fila), sua saída será confundida novamente:

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 é isso que eu quis dizer quando disse que todas as filas são simultâneas umas em relação às outras. Essas são duas filas seriais executando suas tarefas ao mesmo tempo (porque são filas separadas). Uma fila não conhece ou se preocupa com outras filas. Agora vamos voltar para duas filas seriais (da mesma fila) e adicionar uma terceira fila, uma concorrente:

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

Isso é meio inesperado, por que a fila simultânea esperou que as filas seriais terminassem antes de ser executada? Isso não é simultaneidade. Seu playground pode mostrar uma saída diferente, mas o meu mostrou isso. E mostrou isso porque a prioridade da minha fila simultânea não era alta o suficiente para o GCD executar sua tarefa mais cedo. Portanto, se eu mantiver tudo igual, mas alterar a QoS da fila global (sua qualidade de serviço, que é simplesmente o nível de prioridade da fila) let concurrentQueue = DispatchQueue.global(qos: .userInteractive), a saída será a esperada:

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

As duas filas seriais executaram suas tarefas em série (como esperado) e a fila simultânea executou sua tarefa mais rapidamente porque recebeu um nível de alta prioridade (um alto QoS ou qualidade de serviço).

Duas filas simultâneas, como em nosso primeiro exemplo de impressão, mostram uma impressão confusa (como esperado). Para fazer com que eles imprimam perfeitamente em serial, teríamos que fazer com que ambos tenham a mesma fila serial (a mesma instância dessa fila, não apenas a mesma etiqueta) . Então, cada tarefa é executada em série em relação à outra. Outra maneira, no entanto, de fazer com que eles imprimam em série é mantê-los simultâneos, mas alterando seu método de envio:

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

Lembre-se de que o envio de sincronização significa apenas que o thread de chamada espera até que a tarefa na fila seja concluída antes de prosseguir. A ressalva aqui, obviamente, é que o thread de chamada é congelado até que a primeira tarefa seja concluída, o que pode ou não ser como você deseja que a IU funcione.

E é por esta razão que não podemos fazer o seguinte:

DispatchQueue.main.sync { ... }

Esta é a única combinação possível de filas e métodos de despacho que não podemos realizar - despacho síncrono na fila principal. E isso porque estamos pedindo que a fila principal congele até que executemos a tarefa entre as chaves ... que despachamos para a fila principal, que acabamos de congelar. Isso é chamado de deadlock. Para vê-lo em ação em um playground:

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

Uma última coisa a mencionar são os recursos. Quando atribuímos uma tarefa a uma fila, o GCD encontra uma fila disponível em seu pool gerenciado internamente. Até a redação desta resposta, existem 64 filas disponíveis por qos. Isso pode parecer muito, mas eles podem ser consumidos rapidamente, especialmente por bibliotecas de terceiros, especialmente estruturas de banco de dados. Por esse motivo, a Apple tem recomendações sobre gerenciamento de filas (mencionadas nos links abaixo); sendo um:

Em vez de criar filas simultâneas privadas, envie tarefas para uma das filas de despacho simultâneas globais. Para tarefas seriais, defina o destino de sua fila serial para uma das filas simultâneas globais. Dessa forma, você pode manter o comportamento serializado da fila enquanto minimiza o número de filas separadas criando threads.

Para fazer isso, em vez de criá-los como fizemos antes (o que você ainda pode), a Apple recomenda a criação de filas seriais como este:

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

Para ler mais, recomendo o seguinte:

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
fonte
7

Se bem entendi como funciona o GCD, acho que existem dois tipos de DispatchQueue, seriale concurrent, ao mesmo tempo, há duas maneiras de DispatchQueuedespachar suas tarefas, a atribuída closure, a primeira é asynce a outra é sync. Esses juntos determinam como o encerramento (tarefa) realmente é executado.

Eu descobri isso seriale concurrentsignifica quantos threads essa fila pode usar, serialsignifica um, enquanto concurrentsignifica muitos. E synce asyncsignificar a tarefa será executada em que linha, linha do chamador ou o fio subjacente essa fila, syncmeios executado em linha do chamador enquanto asyncmeios executado no thread subjacente.

O seguinte é um código experimental que pode ser executado no playground do 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()

Espero que possa ser útil.

Keith
fonte
7

Gosto de pensar isso usando esta metáfora (aqui está o link para a imagem original):

Papai vai precisar de ajuda

Vamos imaginar que seu pai está lavando a louça e você acabou de tomar um copo de refrigerante. Você traz o copo para seu pai limpar, colocando-o ao lado do outro prato.

Agora seu pai está lavando a louça sozinho, então ele terá que lavá-los um por um: Seu pai aqui representa uma fila serial .

Mas você não está realmente interessado em ficar ali olhando tudo ser limpo. Então, você deixa cair o copo e volta para o seu quarto: isso é chamado de despacho assíncrono . Seu pai pode ou não deixar você saber quando ele terminar, mas o importante é que você não está esperando que o vidro seja limpo; você volta para o seu quarto para fazer coisas de criança.

Agora, vamos supor que você ainda esteja com sede e queira um pouco de água no mesmo copo que por acaso é seu favorito, e você realmente quer de volta assim que for limpo. Então, você fica lá e observa seu pai lavando a louça até que a sua esteja pronta. Este é um envio de sincronização , já que você está bloqueado enquanto espera a tarefa ser concluída.

E, finalmente, digamos que sua mãe decida ajudar seu pai e se juntar a ele para lavar a louça. Agora a fila se torna uma fila simultânea, pois eles podem limpar vários pratos ao mesmo tempo; mas observe que você ainda pode decidir esperar lá ou voltar para o seu quarto, independentemente de como funcionem.

Espero que isto ajude

Yunus Nedim Mehel
fonte
3

1. Estou lendo que filas seriais são criadas e usadas para executar tarefas uma após a outra. No entanto, o que acontece se: - • Eu criar uma fila serial • Eu uso dispatch_async (na fila serial que acabei de criar) três vezes para despachar três blocos A, B, C

RESPOSTA : - Todos os três blocos executados um após o outro. Criei um código de amostra que ajuda a entender.

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
fonte