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.
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
Estou lendo que posso usar
dispatch_sync
em 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!
ios
multithreading
concurrency
grand-central-dispatch
Bogdan Alexandru
fonte
fonte
Respostas:
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.
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.
fonte
Aqui estão algumas das experiências que eu tenho feito para me fazer entender sobre estes
serial
,concurrent
filas comGrand Central Dispatch
.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/async
enquanto 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 fundoEDITAR: 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.
EDITAR EDITAR: Você pode assistir ao vídeo de demonstração aqui
fonte
}
porque ele realmente não está executando naquele momentoconcurrentQueue.sync
ofdoLongSyncTaskInConcurrentQueue()
, ele imprime o thread principal,Task will run in different thread
parece não ser verdade.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:
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:
Você pode torná-lo simultâneo por meio de sua propriedade de atributo:
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):
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:
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:
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:
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:
Digamos que despachamos duas filas simultâneas em assíncrono:
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:
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:
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:
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:
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: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:
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:
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:
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:
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:
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
fonte
Se bem entendi como funciona o GCD, acho que existem dois tipos de
DispatchQueue
,serial
econcurrent
, ao mesmo tempo, há duas maneiras deDispatchQueue
despachar suas tarefas, a atribuídaclosure
, a primeira éasync
e a outra ésync
. Esses juntos determinam como o encerramento (tarefa) realmente é executado.Eu descobri isso
serial
econcurrent
significa quantos threads essa fila pode usar,serial
significa um, enquantoconcurrent
significa muitos. Esync
easync
significar a tarefa será executada em que linha, linha do chamador ou o fio subjacente essa fila,sync
meios executado em linha do chamador enquantoasync
meios executado no thread subjacente.O seguinte é um código experimental que pode ser executado no playground do Xcode.
Espero que possa ser útil.
fonte
Gosto de pensar isso usando esta metáfora (aqui está o link para a imagem original):
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
fonte
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.
fonte