Qual é a diferença entre chamadas assíncronas e sem bloqueio? Também entre chamadas bloqueadas e síncronas (com exemplos, por favor)?
Qual é a diferença entre chamadas assíncronas e sem bloqueio? Também entre chamadas bloqueadas e síncronas (com exemplos, por favor)?
Respostas:
Em muitas circunstâncias, são nomes diferentes para a mesma coisa, mas em alguns contextos são bem diferentes. Então depende. A terminologia não é aplicada de maneira totalmente consistente em toda a indústria de software.
Por exemplo, na API de soquetes clássicos, um soquete sem bloqueio é aquele que simplesmente retorna imediatamente com uma mensagem de erro especial "bloquearia", enquanto um soquete de bloqueio teria bloqueado. Você precisa usar uma função separada, como select
ou poll
para descobrir quando é um bom momento para tentar novamente.
Mas soquetes assíncronos (como suportados pelos soquetes do Windows), ou o padrão de E / S assíncrono usado no .NET, são mais convenientes. Você chama um método para iniciar uma operação, e a estrutura chama de volta quando terminar. Mesmo aqui, existem diferenças básicas. Os soquetes Win32 assíncronos "empacotam" seus resultados em um thread específico da GUI passando mensagens do Windows, enquanto o IO assíncrono do .NET é de thread livre (você não sabe em qual thread seu retorno de chamada será chamado).
Então eles nem sempre significam a mesma coisa. Para destilar o exemplo do soquete, poderíamos dizer:
síncrono / assíncrono é descrever a relação entre dois módulos.
bloqueio / não bloqueio é descrever a situação de um módulo.
Um exemplo:
Módulo X: "I".
Módulo Y: "livraria".
X pergunta a Y: você tem um livro chamado "c ++ primer"?
1) bloqueio: antes que Y responda X, X fica esperando lá pela resposta. Agora o X (um módulo) está bloqueando. X e Y são dois threads ou dois processos ou um thread ou um processo? nós não sabemos.
2) sem bloqueio: antes de Y responder X, X simplesmente sai de lá e faz outras coisas. X pode voltar a cada dois minutos para verificar se Y terminou seu trabalho? Ou X não voltará até que Y ligue para ele? Nós não sabemos. Sabemos apenas que X pode fazer outras coisas antes que Y termine seu trabalho. Aqui o X (um módulo) não é bloqueador. X e Y são dois threads ou dois processos ou um processo? nós não sabemos. MAS temos certeza de que X e Y não podem ser um segmento.
3) síncrona: antes de Y responder X, X fica esperando lá pela resposta. Isso significa que X não pode continuar até que Y termine seu trabalho. Agora dizemos: X e Y (dois módulos) são síncronos. X e Y são dois threads ou dois processos ou um thread ou um processo? nós não sabemos.
4) assíncrono: antes de Y responder X, X sai de lá e X pode fazer outros trabalhos. X não voltará até que Y ligue para ele. Agora dizemos: X e Y (dois módulos) são assíncronos. X e Y são dois threads ou dois processos ou um processo? nós não sabemos. MAS temos certeza de que X e Y não podem ser um segmento.
Por favor, preste atenção nas duas frases em negrito acima. Por que a frase em negrito no 2) contém dois casos, enquanto a frase em negrito no 4) contém apenas um caso? Essa é a chave da diferença entre não-bloqueio e assíncrona.
Aqui está um exemplo típico de não-bloqueio e síncrono:
// thread X
while (true)
{
msg = recv(Y, NON_BLOCKING_FLAG);
if (msg is not empty)
{
break;
}
sleep(2000); // 2 sec
}
// thread Y
// prepare the book for X
send(X, book);
Você pode ver que esse design é sem bloqueio (você pode dizer que na maioria das vezes esse loop faz algo sem sentido, mas aos olhos da CPU, X está em execução, o que significa que X não é bloqueador) enquanto X e Y são síncronos porque X pode continue fazendo outras coisas (X não pode pular para fora do loop) até que ele receba o livro de Y.
Normalmente, nesse caso, o bloqueio do X é muito melhor porque o não bloqueio gasta muito recurso para um loop estúpido. Mas este exemplo é bom para ajudá-lo a entender o fato: não-bloquear não significa assíncrono.
As quatro palavras nos confundem facilmente, o que devemos lembrar é que as quatro palavras servem para o design da arquitetura. Aprender sobre como projetar uma boa arquitetura é a única maneira de distingui-las.
Por exemplo, podemos projetar esse tipo de arquitetura:
// Module X = Module X1 + Module X2
// Module X1
while (true)
{
msg = recv(many_other_modules, NON_BLOCKING_FLAG);
if (msg is not null)
{
if (msg == "done")
{
break;
}
// create a thread to process msg
}
sleep(2000); // 2 sec
}
// Module X2
broadcast("I got the book from Y");
// Module Y
// prepare the book for X
send(X, book);
No exemplo aqui, podemos dizer que
Se precisar, também é possível descrever os threads criados no X1 com as quatro palavras.
As coisas mais importantes são: quando usamos síncrona em vez de assíncrona? quando usamos bloqueio em vez de não-bloqueio?
Por que o Nginx é sem bloqueio? Por que o Apache está bloqueando?
Para fazer uma boa escolha, você deve analisar sua necessidade e testar o desempenho de diferentes arquiteturas. Não existe uma arquitetura adequada para várias necessidades.
Colocando essa questão no contexto da NIO e da NIO.2 no java 7, a E / S assíncrona é um passo mais avançado que o não-bloqueio. Com chamadas NIO sem bloqueio de java, seria possível definir todos os canais (SocketChannel, ServerSocketChannel, FileChannel, etc.) como tal, chamando AbstractSelectableChannel.configureBlocking(false)
. Após o retorno dessas chamadas de E / S, no entanto, você provavelmente ainda precisará controlar as verificações, como se e quando ler / gravar novamente etc.
Por exemplo,
while (!isDataEnough()) {
socketchannel.read(inputBuffer);
// do something else and then read again
}
Com a API assíncrona no java 7, esses controles podem ser feitos de maneiras mais versáteis. Uma das 2 maneiras é usar CompletionHandler
. Observe que as duas read
chamadas são sem bloqueio.
asyncsocket.read(inputBuffer, 60, TimeUnit.SECONDS /* 60 secs for timeout */,
new CompletionHandler<Integer, Object>() {
public void completed(Integer result, Object attachment) {...}
public void failed(Throwable e, Object attachment) {...}
}
}
FileChannel
não é selecionável e não pode ser configurado para não bloquear.
Como você provavelmente pode ver da multiplicidade de respostas diferentes (e muitas vezes mutuamente exclusivas), depende de quem você perguntar. Em algumas arenas, os termos são sinônimos. Ou eles podem se referir a dois conceitos semelhantes:
Em ambos os casos, a intenção é permitir que o programa não seja bloqueado, aguardando a conclusão de um processo lento - a expectativa de resposta do programa é a única diferença real. Qual termo refere-se ao qual também muda de programador para programador, idioma para idioma ou plataforma para plataforma. Ou os termos podem se referir a conceitos completamente diferentes (como o uso de síncrono / assíncrono em relação à programação de threads).
Desculpe, mas não acredito que exista uma única resposta certa que seja globalmente verdadeira.
Uma chamada sem bloqueio retorna imediatamente com os dados disponíveis: o número total de bytes solicitados, menos ou nenhum.
Uma chamada assíncrona solicita uma transferência que será executada em sua totalidade (totalidade), mas será concluída em algum momento futuro.
Sem bloqueio: Esta função não espera enquanto estiver na pilha.
Assíncrono: o trabalho pode continuar em nome da chamada de função após a saída da pilha
Síncrono é definido como acontecendo ao mesmo tempo.
Assíncrono é definido como não acontecendo ao mesmo tempo.
É isso que causa a primeira confusão. Síncrono é realmente o que é conhecido como paralelo. Enquanto assíncrono é seqüencial, faça isso e faça isso.
Agora, todo o problema é modelar um comportamento assíncrono, porque você tem alguma operação que precisa da resposta de outra antes que ela possa começar. Portanto, é um problema de coordenação. Como você saberá que agora pode iniciar essa operação?
A solução mais simples é conhecida como bloqueio.
O bloqueio ocorre quando você simplesmente decide esperar pela outra coisa e devolve uma resposta antes de prosseguir para a operação que precisava.
Portanto, se você precisar colocar manteiga na torrada, primeiro precisará torrar a raça. A maneira como você os coordenava é que você primeiro brindava a raça, depois olhava sem parar a torradeira até que ela aparecesse, e então você passava a manteiga nelas.
É a solução mais simples e funciona muito bem. Não há motivo real para não usá-lo, a menos que você tenha outras coisas que precisam ser executadas que não exijam coordenação com as operações. Por exemplo, lavando a louça. Por que esperar ocioso olhando a torradeira constantemente para a torrada estourar, quando você sabe que vai demorar um pouco e pode lavar um prato inteiro enquanto ela termina?
É aí que duas outras soluções conhecidas respectivamente como não bloqueadoras e assíncronas entram em cena.
Não-bloqueio é quando você escolhe fazer outras coisas não relacionadas enquanto espera a execução da operação. Verifique novamente a disponibilidade da resposta como achar melhor.
Então, ao invés de olhar para a torradeira, ela aparece. Você vai e lava um prato inteiro. E então você espia a torradeira para ver se as torradas estalaram. Se não tiverem, você vai lavar outro prato, verificando novamente a torradeira entre cada prato. Quando você vê que as torradas estalaram, você para de lavar a louça e, em vez disso, pega a torrada e passa a colocar manteiga nelas.
Ter que verificar constantemente as torradas pode ser irritante, imagine que a torradeira esteja em outra sala. Entre os pratos, você perde seu tempo indo para a outra sala para conferir a torrada.
Aí vem assíncrono.
Assíncrono é quando você escolhe fazer outras coisas não relacionadas enquanto aguarda a conclusão da operação. Em vez de verificar isso, você delega o trabalho de verificação para outra coisa, pode ser a operação em si ou um observador, e você deve notificar e possivelmente interromper você quando a resposta estiver disponível, para que você possa prosseguir para a outra operação que precisava disso.
É uma terminologia estranha. Não faz muito sentido, pois todas essas soluções são maneiras de criar coordenação assíncrona de tarefas dependentes. É por isso que prefiro chamá-lo de evento.
Portanto, para este, você decide atualizar sua torradeira para que apite quando as torradas terminarem. Você está constantemente ouvindo, mesmo enquanto lava a louça. Ao ouvir o sinal sonoro, você coloca em fila na memória que, assim que terminar de lavar o prato atual, vai parar e colocar a manteiga na torrada. Ou você pode optar por interromper a lavagem do prato atual e lidar com a torrada imediatamente.
Se você tiver problemas para ouvir o sinal sonoro, peça para o seu parceiro assistir a torradeira e avisar quando a torrada estiver pronta. O seu parceiro pode escolher qualquer uma das três estratégias acima para coordenar sua tarefa de assistir à torradeira e dizer quando está pronta.
Em uma nota final, é bom entender que, apesar de não-bloqueio e assíncrono (ou o que eu prefiro chamar de evento) permitem que você faça outras coisas enquanto espera, mas também não. Você pode optar por verificar constantemente o status de uma chamada sem bloqueio, sem fazer mais nada. No entanto, muitas vezes é pior do que bloquear (como olhar para a torradeira, depois para longe e depois para trás até que esteja pronto); portanto, muitas APIs sem bloqueio permitem que você faça a transição para um modo de bloqueio. Para eventos, você pode apenas esperar inativo até ser notificado. A desvantagem nesse caso é que adicionar a notificação era complexo e potencialmente caro para começar. Você precisou comprar uma nova torradeira com funcionalidade de bipe ou convencer seu parceiro a assistir por você.
E mais uma coisa, você precisa perceber as vantagens e desvantagens que os três oferecem. Um não é obviamente melhor que os outros. Pense no meu exemplo. Se a sua torradeira for tão rápida, você não terá tempo para lavar a louça, nem mesmo começar a lavá-la, é a velocidade da torradeira. Começar outra coisa nesse caso é apenas uma perda de tempo e esforço. O bloqueio serve. Da mesma forma, se lavar um prato demorar 10 vezes mais do que a torrada. Você precisa se perguntar o que é mais importante a ser feito? A torrada pode ficar fria e dura a essa altura, não vale a pena, o bloqueio também serve. Ou você deve escolher coisas mais rápidas para fazer enquanto espera. É mais óbvio, mas minha resposta já é bastante longa, o que quero dizer é que você precisa pensar sobre tudo isso e as complexidades de implementar cada uma para decidir se vale a pena e se vale a pena.
Editar:
Mesmo que isso já seja longo, também quero que esteja completo, então adicionarei mais dois pontos.
1) Também existe geralmente um quarto modelo conhecido como multiplexado . É quando você espera por uma tarefa, inicia outra e, enquanto espera pelas duas, inicia mais uma e assim por diante, até ter muitas tarefas iniciadas e depois esperar inativo, mas em todas eles. Assim que tudo estiver pronto, você poderá prosseguir com o tratamento da resposta e voltar a esperar pelos outros. É conhecido como multiplexado, porque enquanto você espera, você precisa verificar cada tarefa uma após a outra para ver se elas foram concluídas, ad vitam, até que uma seja. É um pouco de uma extensão além do normal, sem bloqueio.
No nosso exemplo, seria como iniciar a torradeira, a máquina de lavar louça, o micro-ondas, etc. E então esperar por uma delas. Onde você verificaria a torradeira para ver se está pronto; caso contrário, verificaria a máquina de lavar louça, se não estiver, o micro-ondas e o ambiente novamente.
2) Embora eu acredite que seja um grande erro, síncrono é frequentemente usado para significar uma coisa de cada vez. E assíncrono muitas coisas ao mesmo tempo. Assim, você verá o bloqueio síncrono e o não-bloqueio usados para se referir a bloqueio e não-bloqueio. E bloqueio assíncrono e sem bloqueio costumavam se referir a multiplexados e a eventos.
Eu realmente não entendo como chegamos lá. Mas quando se trata de E / S e computação, síncrono e assíncrono geralmente se referem ao que é mais conhecido como não sobreposto e sobreposto. Ou seja, assíncrono significa que IO e Computação estão sobrepostas, ou seja, acontecendo simultaneamente. Enquanto síncrono significa que não, isso acontece sequencialmente. Para o não-bloqueio síncrono, isso significa que você não inicia outro IO ou computação, apenas espera e simula uma chamada de bloqueio. Eu gostaria que as pessoas parassem de usar síncrona e assíncrona assim. Então, eu não estou encorajando isso.
Bloqueio de chamada: o controle retorna somente quando a chamada é concluída.
Chamada sem bloqueio : o controle retorna imediatamente. O SO posterior, de alguma forma, notifica o processo de que a chamada está concluída.
Programa síncrono : um programa que usa o bloqueio de chamadas. Para não congelar durante a chamada, ele deve ter 2 ou mais threads (é por isso que é chamado Synchronous - os threads estão em execução de forma síncrona).
Programa assíncrono : um programa que usa chamadas não bloqueadas . Ele pode ter apenas 1 thread e ainda permanecer interativo.
Eles diferem apenas na ortografia. Não há diferença no que eles se referem. Para ser técnico, você poderia dizer que eles diferem em ênfase. Não-bloqueio refere-se ao fluxo de controle (não é bloqueado.) Assíncrono refere-se a quando o evento \ data é tratado (não de forma síncrona).
Os modelos de bloqueio exigem que o aplicativo inicial seja bloqueado quando a E / S for iniciada. Isso significa que não é possível sobrepor processamento e E / S ao mesmo tempo. O modelo síncrono sem bloqueio permite sobreposição de processamento e E / S, mas requer que o aplicativo verifique o status da E / S de forma recorrente. Isso deixa E / S sem bloqueio assíncrona, que permite sobreposição de processamento e E / S, incluindo notificação de conclusão de E / S.
Bloqueio: o controle retorna para invocar a precess após o processamento do primitivo (sincronização ou assíncrona)
Sem bloqueio: o controle retorna ao processo imediatamente após a chamada