Unity coroutine vs threads

13

Qual é a diferença entre uma corotina e uma linha? Existem vantagens em usar um sobre o outro?

StrengthPlusPlus
fonte
3
Coroutines trabalhando em um thread principal.
Woltus 6/07
2
Você não pode usar nenhuma função do Unity em um Thread. Corotinas podem.
Hellium
1
@Hellium Correction, você não pode usar nenhuma função do Unity em um thread diferente, ou seja, as funções do Unity podem ser usadas no thread principal .
Pharap

Respostas:

24

Embora as Coroutines pareçam funcionar como threads à primeira vista, na verdade elas não estão usando nenhum multithreading. Eles são executados sequencialmente até eles yield. O mecanismo verificará todas as corotinas produzidas como parte de seu próprio loop principal (em que momento depende exatamente do tipo de yield, verifique este diagrama para obter mais informações ), continue-as uma após a outra até a próxima yielde prossiga com o loop principal.

Essa técnica tem a vantagem de poder usar corotinas sem as dores de cabeça causadas por problemas com multithreading real. Você não terá conflitos, condições de corrida ou problemas de desempenho causados ​​por alternâncias de contexto, poderá depurar corretamente e não precisará usar contêineres de dados seguros para threads. Isso ocorre porque quando uma corotina está sendo executada, o mecanismo do Unity fica em um estado controlado. É seguro usar a maioria das funcionalidades do Unity.

Com os threads, por outro lado, você não tem absolutamente nenhum conhecimento sobre o estado do loop principal do Unity no momento (ele pode de fato não estar mais em execução). Portanto, seu segmento pode causar muitos estragos ao fazer algo de cada vez que não deveria fazer isso. Não toque em nenhuma funcionalidade nativa do Unity em um sub-thread . Se você precisar se comunicar entre um sub-thread e seu thread principal, faça com que o thread grave em algum objeto container (!) Seguro para threads e faça com que um MonoBehaviour leia essas informações durante as funções usuais do evento Unity.

A desvantagem de não executar multithreading "real" é que você não pode usar corotinas para paralelizar cálculos intensos da CPU em vários núcleos da CPU. Você pode usá-los, no entanto, para dividir um cálculo em várias atualizações. Então, em vez de congelar seu jogo por um segundo, você obtém uma taxa de quadros média mais baixa em vários segundos. Mas, nesse caso, você é responsável pela yieldsua rotina sempre que quiser permitir que o Unity execute uma atualização.

Conclusão:

  • Se você deseja usar a execução assíncrona para expressar a lógica do jogo, use corotinas.
  • Se você deseja usar a execução assíncrona para utilizar vários núcleos da CPU, use threads.
Philipp
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
MichaelHouse
10

As corotinas são o que chamamos de Ciência da Computação "multitarefa cooperativa". Eles são uma maneira de vários fluxos diferentes de execução se intercalarem entre si de forma cooperativa. Na multitarefa cooperativa, um fluxo de execução possui propriedade exclusiva e incontestável da CPU até atingir a yield. Nesse ponto, o Unity (ou qualquer estrutura que você esteja usando), tem a opção de mudar para um fluxo de execução diferente. Ele também obtém propriedade exclusiva e incontestável da CPU atéyield funcione.

Threads são o que chamamos de "multitarefa preemptiva". Quando você está usando encadeamentos, a estrutura se reserva o direito de interromper o encadeamento a qualquer momento e passar para outro encadeamento. Não importa onde você esteja. Você pode até parar parcialmente escrevendo uma variável na memória em alguns casos!

Existem prós e contras para cada um. Os contras das corotinas são provavelmente os mais fáceis de entender. Primeiro, as corotinas são executadas em um único núcleo. Se você possui uma CPU com quatro núcleos, as corotinas usarão apenas um dos quatro núcleos. Isso simplifica as coisas, mas pode ser um problema de desempenho em alguns casos. O segundo golpe é que você deve estar ciente de que qualquer corotina pode interromper todo o programa simplesmente recusando-se ayield . Este foi um problema no Mac OS9, há muitos anos. O OS9 suportava apenas multitarefa cooperativa em todo o computador. Se um dos seus programas travasse, ele poderia parar o computador com tanta força que o sistema operacional não conseguia nem processar o texto da mensagem de erro para informar o que aconteceu!

Os profissionais das corotinas são que eles são relativamente fáceis de entender. Os erros que você tem são muito mais previsíveis. Normalmente, eles também exigem menos recursos, o que pode ser útil à medida que você sobe nos 10 dos milhares de corotinas ou threads. O Candid Moon mencionou nos comentários que, se você não estudou os tópicos corretamente, atenha-se às corotinas, e eles estão certos. As corotinas são muito mais simples de se trabalhar.

Threads são um animal completamente diferente. Você sempre deve estar atento à possibilidade de que outro thread possa interrompê-lo a qualquer momentoe mexa com seus dados. As bibliotecas de encadeamento fornecem conjuntos completos de ferramentas poderosas para ajudá-lo, como mutexes e variáveis ​​de condição que ajudam a informar ao sistema operacional quando é seguro executar um de seus outros encadeamentos e quando não é seguro. Existem cursos completos dedicados a como usar essas ferramentas também. Uma das questões famosas que surge é um "impasse", que é quando dois threads ficam "presos" esperando o outro liberar alguns recursos. Outra questão, que é muito importante para o Unity, é que muitas bibliotecas (como o Unity) não foram projetadas para suportar chamadas de vários threads. Você pode facilmente quebrar sua estrutura se não prestar atenção em quais chamadas são permitidas e quais são proibidas.

A razão para essa complexidade extra é realmente muito simples. O modelo multitarefa preemptivo é realmente semelhante ao modelo multithreading , que permite não apenas interromper outros threads, mas também executar threads lado a lado em núcleos diferentes. Isso é incrivelmente poderoso, sendo a única maneira de realmente alavancar essas novas CPUs quad core e código hexadecimal que estão saindo, mas abre a caixa de pandoras. As regras de sincronização de como gerenciar esses dados em um mundo com vários segmentos são positivamente brutais. No mundo C ++, há artigos inteiros dedicados aos MEMORY_ORDER_CONSUMEquais é um canto de sincronização multithreading.

Então, os contras de enfiar? Simples: são difíceis. Você pode encontrar classes inteiras de bugs que você nunca viu antes. Muitos são chamados "heisenbugs" que às vezes aparecem e desaparecem quando você os depura. As ferramentas fornecidas para lidar com elas são muito poderosas, mas também são de nível muito baixo. Eles foram projetados para serem eficientes nas arquiteturas dos chips modernos, em vez de serem projetados para serem fáceis de usar.

No entanto, se você quiser usar toda a energia da CPU, eles são a ferramenta que você precisa. Além disso, na verdade existem algoritmos que são mais fáceis de entender no multithreading do que nas corotinas, simplesmente porque você deixa o sistema operacional lidar com todas as perguntas sobre onde as interrupções podem ocorrer.

O comentário de Cândido Moon para manter as corotinas também é minha recomendação. Se você deseja o poder dos encadeamentos, comprometa-se com ele. Sair e realmente aprender tópicos, formalmente. Tivemos várias décadas para descobrir como organizar a melhor maneira de pensar em encadeamentos para que você obtenha resultados repetíveis seguros e confiáveis ​​com antecedência, e adicione desempenho à medida que avança. Por exemplo, todos os cursos sãos ensinam mutexes antes de ensinar variáveis ​​de condição. Todos os cursos sãos que cobrem os átomos ensinam completamente mutexes e variáveis ​​de condição antes mesmo de mencionar que existem átomos. (Nota: não existe um tutorial sensato sobre atômica.) Tente aprender a segmentar aos poucos e você está implorando por uma enxaqueca.

Cort Ammon
fonte
Os encadeamentos adicionam complexidade principalmente nos casos em que operações e dependências são intercaladas. Se o loop principal do jogo contiver três funções, x (), y () e z (), e nenhuma delas afetar qualquer coisa necessária a outra, inicie as três simultaneamente, mas aguarde a conclusão de todas antes de prosseguir. permita que alguém receba algum benefício de uma máquina com vários núcleos sem ter que adicionar muita complexidade. Enquanto variáveis de condição são geralmente usados em conjunto com semáforos, o paradigma-paralelas-passos independentes realmente não precisa de semáforos como tal ...
supercat
... mas simplesmente uma maneira de os threads que lidam com y () e z () aguardarem até serem acionados e, em seguida, uma maneira de o thread principal esperar, depois de executar x (), até y () e z () Completou.
Supercat
@supercat Você sempre precisa ter alguma sincronização para fazer a transição entre fases paralelas independentes e fases seqüenciais. Às vezes isso não passa de um join(), mas você precisa de algo. Se você não tem um arquiteto que projetou um sistema para fazer a sincronização para você, você deve escrever isso sozinho. Como alguém que faz coisas multithreaded, acho modelos mentais que das pessoas de como necessidade computadores trabalho ajustando antes de fazer a sincronização com segurança (que é o que bons cursos vão ensinar)
Cort Ammon
É claro que precisa haver alguma sincronização, e alcançar a máxima eficiência geralmente exige algo mais do que isso join. Meu argumento era que buscar uma fração moderada dos possíveis benefícios de desempenho do encadeamento, facilmente, às vezes pode ser melhor do que usar uma abordagem de encadeamento mais complicada para obter uma fração maior.
Supercat
Sobre novos tipos de bugs: observe também que, se você não puder provar logicamente que o código é seguro, é impossível saber como o buggy estará fora. Embora a ordem de execução possa ser indefinida, geralmente ela usa caminhos semelhantes sempre que você a executa em uma máquina. Muitas vezes, você descobrirá que ela falha muito e frequentemente em uma fração de máquinas, e não é possível testar em todas as máquinas possíveis. No trabalho, tivemos um bug que só se manifestava em uma de nossas máquinas, apenas se o log estava desativado; demorou muito tempo para localizar várias pessoas. Não quero isso na natureza. Não apenas teste a sincronização, prove logicamente.
Aaron
2

Nos termos mais simples possíveis ...


Tópicos

Um encadeamento não decide quando produz, o sistema operacional (o 'SO', por exemplo, Windows) decide quando um encadeamento é produzido. O sistema operacional é quase inteiramente responsável pelo agendamento de threads, decide quais threads executar, quando executá-los e por quanto tempo.

Além disso, um encadeamento pode ser executado de forma síncrona (um encadeamento após outro) ou de forma assíncrona (encadeamentos diferentes executados em núcleos de CPU diferentes). A capacidade de executar de forma assíncrona significa que os threads podem realizar mais trabalhos na mesma quantidade de tempo (porque os threads literalmente estão fazendo duas coisas ao mesmo tempo). Até os encadeamentos síncronos realizam muito trabalho se o sistema operacional for bom em planejá-los.

No entanto, esse poder de processamento extra vem com efeitos colaterais. Por exemplo, se dois threads estão tentando acessar o mesmo recurso (por exemplo, uma lista) e cada thread pode ser interrompido aleatoriamente em qualquer ponto do código, as modificações do segundo thread podem interferir nas modificações feitas pelo primeiro thread. (Veja também: Condições da corrida e deadlock .)

Os encadeamentos também são considerados 'pesados' porque possuem muita sobrecarga, o que significa que há uma penalidade de tempo considerável ao trocar os encadeamentos.


Coroutines

Diferentemente dos encadeamentos, as corotinas são completamente síncronas, apenas uma corotina pode estar em execução a qualquer momento. Além disso, as corotinas escolhem quando produzir e, portanto, podem optar por produzir em um ponto conveniente no código (por exemplo, no final de um ciclo de loop). Isso tem a vantagem de problemas como condições de corrida e impasses muito mais fáceis de serem evitados, além de facilitar a cooperação das corotinas.

No entanto, também é uma grande responsabilidade, se uma corotina não produzir adequadamente, poderá acabar consumindo muito tempo do processador e ainda poderá causar erros se modificar incorretamente os recursos compartilhados.

As corotinas geralmente não exigem mudança de contexto e, portanto, são rápidas para entrar e sair e são bastante leves.


Em suma:

Fio:

  • Síncrono ou Assíncrono
  • Produzido pelo SO
  • Produzido aleatoriamente
  • Pesado

Corotina:

  • Síncrono
  • Produz auto
  • Rendimentos por escolha
  • Leve

Os papéis dos threads e das corotinas são muito semelhantes, mas diferem na maneira como realizam o trabalho, o que significa que cada um é mais adequado para tarefas diferentes. Os encadeamentos são melhores para tarefas nas quais eles podem se concentrar em fazer algo por conta própria sem serem interrompidos e, em seguida, sinalizar quando terminarem. As corotinas são melhores para tarefas que podem ser executadas em várias etapas pequenas e tarefas que exigem o processamento cooperativo de dados.

Pharap
fonte