Exemplos concretos
Gostaria de adicionar alguns exemplos do mundo real e conectá-los ao mundo da engenharia de software. Primeiro, considere algo que, espero, corresponda à sua definição intuitiva de "síncrona": o piscar de vaga-lumes , sob algumas circunstâncias. Segundo, considere a corrida olímpica feminina de revezamento 4x100 . Terceiro, considere o velho argumento dos filmes militares: "Homens, sincronizem seus relógios!"
Agora, vamos pensar no que está acontecendo. Vamos começar observando que todas essas coisas são processos ou entidades prolongadas no tempo . Não faz sentido dizer que uma tigela é "síncrona" e a rocha é "assíncrona". Segundo, são necessários dois para dançar o tango . Você não pode dizer que "um corredor é sincronizado". Sincronizar com o que? Finalmente, para que dois processos façam algo ao mesmo tempo, a menos que eles já tenham exatamente a mesma frequência e fase, um ou os dois devem esperar .
Análise
Quando a definição do dicionário diz que duas entidades sincronizadas "ocorrem ou existem ao mesmo tempo", isso se alinha muito bem ao conceito de luz dos vaga-lumes. Infelizmente, dizer que a luz está "sincronizada" é uma maneira desleixada de dizer que os processos de iluminação do vaga - lume estão sincronizados.
Então, como um grupo de vaga-lumes, que presumivelmente não têm o Apple SmartWatch e o NTP para guiá-los, conseguem piscar as extremidades traseiras ao mesmo tempo? Bem, é muito fácil se eles tiverem meios para definir um ritmo consistente e puderem fazer pequenos ajustes. Eles apenas piscam e, se mais pessoas piscam logo após eles, diminuem a velocidade (aumentam o atraso), enquanto que se mais piscam logo antes deles, eles aceleram (diminuem o atraso). Assim, eles podem usar um processo simples de feedback para chegar essencialmente ao mesmo ritmo e fase. A observação importante aqui é notar que eles alcançam a sincronia, aguardando o momento certo para piscar .
A corrida 4x100 é interessante porque você vê as duas formas de sincronismo de processos em ação: os corredores dentro de uma equipe são sincronizados, enquanto os corredores em diferentes equipes são "assíncrono". O segundo corredor no relé deve esperar até que o primeiro corredor entre na zona de transferência . A entrega é um evento síncrono entre esses dois corredores. No entanto, os corredores de diferentes faixas não se importam com o que está acontecendo em outra faixa e, certamente, não diminuem a velocidade e fazem as transferências em sincronia. Cada faixa de corredores é assíncrona entre si. Novamente, vemos que a sincronização implica em espera, enquanto a assincronia não.
Por fim, os soldados de uma empresa (pelotão, equipe de bombeiros etc.) devem sincronizar seus relógios para que possam atacar o inimigo ao mesmo tempo . Pode ser que alguns soldados cheguem a suas posições antes de outros, ou tenham a oportunidade de disparar contra o inimigo antes. Mas um ataque simultâneo é geralmente mais eficaz do que um ataque aleatório por causa do elemento surpresa. Portanto, para alcançar a sincronia, muitos soldados precisam esperar o tempo determinado para agir.
Característica definidora
Por que essa ênfase na espera? Bem, é porque a espera é a característica que define os processos síncronos e assíncronos. Se você possui dois processos sobre os quais não conhece nada, deve, por padrão, assumir que eles são assíncronos. Por exemplo, uma entrega de pacotes e uma ambulância provavelmente não estão sincronizadas. Para demonstrar que dois processos são, de fato, sincronizados, você precisa encontrar um momento muito especial no tempo: o ponto de sincronização .
Um motorista de entrega que entrega um pacote e uma ambulância que leva alguém para o hospital geralmente não compartilham pontos no tempo que identificamos como um "ponto de sincronização". Por outro lado, os vaga-lumes piscando em uníssono têm um ponto de sincronização toda vez que piscam, os corredores de revezamento têm um ponto de sincronização toda vez que entregam o bastão, e os soldados têm um ponto de sincronização quando lançam seu ataque. Se você conseguir identificar um ou mais pontos de sincronização, os processos serão sincronizados . Isso deve ser fácil de entender, porque "syn-" é um prefixo grego que significa "com" ou "juntos" e "crono" é a raiz grega de "tempo". "Sincronizado" significa literalmente "ao mesmo tempo",
Limites
Observe que a "sincronização" não se aplica necessariamente a toda a vida útil de um ou de ambos os processos. Eu argumentaria que isso se aplica apenas ao "tempo de espera até o (s) ponto (s) de sincronização". Assim, dois processos podem operar de forma assíncrona até atingirem um estado em que precisam se comunicar, depois se tornam sincronizados, trocam informações e depois continuam de forma assíncrona. Um exemplo simples é conhecer alguém para tomar um café. Obviamente, a reunião é um ponto de sincronização (ou mais), e o fato de duas pessoas chegarem nesse ponto demonstra a sincronia. No entanto, não diríamos que, porque duas pessoas se encontraram para tomar café, essas duas vidas humanassão "sincronizados". Pode ser que esse foi o único instante em suas vidas que eles se conheceram, e tudo o mais que eles fazem é independente.
Também não é o caso de encontros incidentais demonstrarem sincronia. Se dois estranhos se cruzam na rua, o fato de estarem em um determinado lugar em algum momento não prova sincronia. Tampouco o fato de uma pessoa estar sentada em um banco esperando o ônibus e outra passar por ali. Os processos são síncronos apenas quando se encontram para um propósito .
Conexão de Software
Agora, vamos pensar em uma tarefa muito fundamental no software: ler de um arquivo. Como você provavelmente sabe, o armazenamento em massa geralmente é milhares a milhões de vezes mais lento que o cache ou a memória principal. Por esse motivo, os sistemas operacionais e as bibliotecas de linguagens de programação geralmente oferecem operações de E / S síncronas e assíncronas. Agora, mesmo que seu programa tenha apenas um único encadeamento, você deve pensar no sistema operacional como um "processo separado" para os fins desta discussão.
Sincronizar
Quando você faz uma "leitura de E / S síncrona", seu encadeamento deve esperar até que os dados estejam disponíveis; nesse ponto, eles continuam. Isso é muito parecido com um corredor de revezamento entregando o bastão para o próximo corredor, mas imagine um revezamento com apenas dois corredores percorrendo toda a pista, e o segundo corredor também volta para o primeiro.
Nesse caso, o encadeamento do programa e o processo de E / S do SO não estão "acontecendo (atuando) ao mesmo tempo" e, portanto, parece estranho dizer que esses processos estão "sincronizados". Mas essa é a maneira errada de ver! É como dizer: "Os corredores de uma equipe de revezamento não estão correndo ao mesmo tempo, portanto não estão sincronizados". De fato, ambas as afirmações estão erradas! Os corredores em uma equipe de revezamento fazer e devem ser executados ao mesmo tempo, mas apenas em um momento muito específico: a mão-off do bastão. De fato, é apenas esse momento especial durante a corrida que nos convence de que as equipes de revezamento são sincronizadas para começar! Se visualizarmos a solicitação e resposta de E / S como "o bastão",
Por outro lado, se pensarmos em algo como Análise de Elementos Finitos em um supercomputador, veremos que milhares de processos devem trabalhar em etapas para atualizar um enorme estado global. Mesmo que alguns dos nós concluam seu trabalho por um determinado intervalo de tempo antes de outros, todos precisam aguardar o término do tempo, pois os resultados se propagam para os vizinhos pelo espaço. Esse tipo de sincronização é como os vaga-lumes: todos os atores estão realizando o mesmo tipo de tarefa.
Variedade do processo
Por esse motivo, podemos inventar alguns termos para nos ajudar a ver que existem três tipos de coisas acontecendo: "sincronia homogênea", "sincronia heterogênea" e "sincronia sequencial". Portanto, quando os atores estão realizando a mesma tarefa simultaneamente (FEA, vaga-lume), eles são "homogêneos". Quando eles estão executando tarefas diferentes simultaneamente (soldados correndo vs. rastejando vs. nadando para seus destinos, física vs. som vs. threads de IA em um jogo), eles são "heterogêneos". Quando eles estão executando tarefas uma por vez, são "sequenciais" (corredores de retransmissão, bloqueio de E / S). Eles podem parecer muito diferentes, mas compartilham uma propriedade essencial: todos os tipos de atores esperam um pouco para garantir que todos cheguem ao ponto de sincronização ao mesmo tempo. entre os pontos de sincronização, ou "executar a mesma ação" é irrelevante para a propriedade da sincronicidade.
Os pipelines de renderização em uma GPU são síncronos porque todos devem terminar o quadro juntos e iniciar um novo quadro juntos. Eles são homogêneos porque estão fazendo o mesmo tipo de trabalho e são todos ativos juntos. Mas o loop principal do jogo de um servidor e os threads de E / S de bloqueio que processam a entrada remota são heterogêneos porque eles fazem tipos muito diferentes de trabalho, e alguns dos threads de E / S não farão nada, porque nem todos as conexões são usadas. Mesmo assim, eles são sincronizados, porque precisam compartilhar o estado atomicamente (um jogador não deve ver uma atualização parcial do mundo do jogo, nem o servidor deve ver apenas um fragmento da entrada do jogador).
Assíncrono
Agora, vamos considerar uma "leitura assíncrona de E / S". Quando seu programa envia uma solicitação ao sistema operacional para ler um pouco de dados do armazenamento, a chamada retorna imediatamente . Vamos ignorar retornos de chamada e focar na pesquisa. Em geral, o momento em que os dados estão disponíveis para o seu programa não corresponde a nenhum momento especial no que diz respeito ao encadeamento do programa. Se o seu programa não estiver explicitamente aguardando os dados, o encadeamento nem saberá exatamente quando esse momento ocorrerá. Ele descobrirá apenas que os dados estão aguardando na próxima vez em que forem verificados.
Não há horário de reunião especial em que o SO e o encadeamento do programa concordam em entregar os dados. Eles são como dois navios passando na noite. A assincronia é caracterizada por essa ausência de espera. Obviamente, o encadeamento do programa geralmente acaba aguardando a operação de E / S, mas não precisa. Felizmente, ele pode continuar fazendo outros cálculos enquanto a busca de E / S está ocorrendo e verificar apenas mais tarde quando houver um momento de sobra. Obviamente, quando o sistema operacional busca os dados, ele também não fica esperando. Ele coloca os dados em algum lugar conveniente e continua seus negócios. Nesse caso, é como se o programa entregasse o bastão ao sistema operacional, e o sistema operacional aparecesse mais tarde, largasse o bastão no chão junto com os dados e saísse da pista. O programa pode ou não estar esperando para receber a transferência.
Paralelismo
Quando marcamos uma função como "assíncrona" no software, geralmente significa que queremos paralelismo . Mas lembre-se de que o paralelismo não implica sincronia . Os vaga-lumes são um bom exemplo, porque também exibem comportamento síncrono e assíncrono. Enquanto a maioria das moscas brilhava em uníssono, muitas estavam obviamente fora de sintonia com o resto do grupo e brilhavam mais aleatoriamente. As moscas podem estar agindo simultaneamente , mas nem todas foram sincronizadas .
Agora, quando marcamos algum código como "assíncrono", parece engraçado, porque implica que o restante do código não tão marcado é "sincronização". Afinal, o que isso quer dizer? Não insistimos que a "sincronização" exigia dois para dançar? Mas e se estivermos falando sobre a execução de código em um único thread? Nesse caso, precisamos dar um passo atrás e pensar em um programa como uma sequência de estados e transições entre esses estados. Uma declaração em um programa causa uma transição de estado. Podemos pensar nisso como um "micro-processo" que começa e termina com a declaração. Os pontos de sequência definidos pelo idioma são, de fato, os pontos de sincronização desses "microprocessos". E, assim, podemos ver um thread único,
A integridade da linguagem de programação garante que as atualizações de estado não interfiram nas instruções e os pontos de sequência definem limites através dos quais o compilador não tem permissão para fazer otimizações observáveis. Por exemplo, a ordem de avaliação das expressões em uma instrução pode ser indefinida ou subespecificada, dando ao compilador a liberdade de otimizar a instrução de várias maneiras. Mas quando a próxima instrução começar, o programa deverá estar em um estado bem definido, se o próprio PL estiver correto.
Até agora, deve ficar claro o que queremos dizer com "assíncrono". Isso significa exatamente que o contrato implícito de sincronia dentro de um bloco de código está isento para o bloco assíncrono. É permitido atualizar o estado do programa independentemente, sem as garantias de segurança normalmente implícitas no modelo de computação seqüencial (consistentemente consistente, síncrono). Obviamente, isso significa que precisamos tomar cuidado especial para não destruir o estado do programa com inconsistência. Isso geralmente significa que introduzimos sincronia limitada e explícita para coordenar com o bloco assíncrono. Observe que isso significa que o bloco assíncrono pode ser assíncrono e síncrono em momentos diferentes! Mas lembrando que a sincronização indica meramente a existência de um ponto de sincronização, não devemos ter problemas em aceitar essa noção.