Nos módulos de programação tradicionais, as operações de E/S são como uma chamada de função local comum: o programa é bloqueado antes da execução da função e não pode continuar sendo executada. A E/S bloqueada se originou do modelo de fatia anterior, onde cada processo é como uma pessoa independente, com o objetivo de distinguir todos, e todos geralmente podem fazer apenas uma coisa ao mesmo tempo e devem esperar a coisa anterior a ser feita antes de decidir o que fazer a seguir. No entanto, esse modelo de "um usuário, um processo" amplamente utilizado nas redes de computadores e na Internet é muito escalável. Ao gerenciar vários processos, ele consome muita memória e mudança de contexto também ocupará muitos recursos. Isso é um enorme fardo para o sistema operacional e, à medida que o número de processos aumenta, o desempenho do sistema decairá acentuadamente.
Multithreading é uma alternativa. Um thread é um processo leve que compartilha memória com outros threads no mesmo processo. É mais como uma extensão do modelo tradicional, que é usado para executar vários threads simultaneamente. Quando um thread está aguardando as operações de E/S, outros threads podem assumir a CPU. Quando a operação de E/S é concluída, o tópico aguardando na frente será despertado. Ou seja, um fio em execução pode ser interrompido e depois retomado posteriormente. Além disso, os threads podem ser executados em paralelo sob diferentes núcleos de CPUs multi-núcleo sob alguns sistemas.
Os programadores não sabem a que horas o thread será executado. Eles devem ter cuidado para lidar com o acesso simultâneo à memória compartilhada, para que eles devem usar alguns primitivos de sincronização para sincronizar o acesso a uma determinada estrutura de dados, como o uso de bloqueios ou semáforos, para forçar os threads a serem executados em comportamentos e planos específicos. As aplicações que dependem fortemente do estado compartilhado entre os threads podem facilmente ter alguns problemas estranhos com forte aleatoriedade e dificuldade em encontrar.
Outra maneira é usar a colaboração com vários threads, onde você é responsável por liberar explicitamente a CPU e entregar o tempo da CPU a outros threads. Como você controla pessoalmente o plano de execução do thread, a necessidade de sincronização é reduzida, mas também aumenta a complexidade do programa e a chance de erros, e não evita os problemas da multi-fibra.
O que é programação orientada a eventos
A programação orientada a eventos é um estilo de programação, onde os eventos determinam o processo de execução de um programa. Os eventos são tratados por manipuladores de eventos ou retornos de chamada de eventos. Os retornos de chamada do evento são funções chamadas quando ocorre um evento específico, como o banco de dados retorna o resultado da consulta ou o usuário clica em um botão.
Lembre -se de que, no modo de programação de E/S bloqueado tradicional, as consultas de banco de dados podem ser assim:
A cópia do código é a seguinte:
resultado = query ('selecione * de postagens onde id = 1');
do_something_with (resultado);
A função de consulta acima manterá o encadeamento ou processo atual em um estado de espera até que o banco de dados subjacente conclua a operação da consulta e retorne.
No modelo orientado a eventos, esta consulta se tornará assim:
A cópia do código é a seguinte:
query_finished = function (resultado) {
do_something_with (resultado);
}
Query ('Selecione * de postagens onde id = 1', query_finished);
Primeiro, você define uma função chamada query_finished, que contém o que você deseja fazer após a conclusão da consulta. Em seguida, passe essa função como um parâmetro para a função de consulta. Query_finished será chamado após a execução da consulta, em vez de apenas retornar o resultado da consulta.
Quando um evento em que você está interessado ocorre, a função que você definirá será chamada em vez de simplesmente retornar o valor do resultado. Este modelo de programação é chamado de programação orientada a eventos ou programação assíncrona. Este é um dos recursos mais óbvios do nó. Esse modelo de programação significa que o processo atual não será bloqueado ao executar operações de E/S. Portanto, várias operações de E/S podem ser executadas em paralelo, e a função de retorno de chamada correspondente será chamada após a conclusão da operação.
A camada subjacente da programação orientada a eventos depende de loops de eventos. Os loops de eventos são basicamente uma estrutura na qual a detecção de eventos e o processador de eventos desencadeia a chamada de loop contínua dessas duas funções. Em cada loop, o mecanismo de loop de eventos precisa detectar quais eventos ocorreram. Quando o evento ocorre, ele encontra a função de retorno de chamada correspondente e a chama.
O loop do evento é apenas um thread em execução no processo. Quando ocorre um evento, o processador de eventos pode ser executado sozinho e não será interrompido, ou seja:
1. No máximo um evento, a função de retorno de chamada está sendo executada em um momento específico
2. Nenhum processador de eventos é interrompido ao executar
Com isso, os desenvolvedores não podem mais ter dores de cabeça sobre a sincronização do encadeamento e a modificação simultânea da memória compartilhada.
Um segredo bem conhecido:
Há muito tempo, as pessoas da comunidade de programação do sistema sabiam que a programação orientada a eventos era a melhor maneira de criar serviços de alta concorrência, porque não precisava economizar muito contexto, por isso economizou muita memória, não tanto a troca de contexto e economizou muito tempo de execução.
Lentamente, esse conceito permeava outras plataformas e comunidades, e surgiram algumas implementações famosas de loop de eventos, como a Machine de Eventos de Ruby, a PERL de Anyevnet e a Twisted de Python. Além disso, existem muitas outras implementações e idiomas.
Para desenvolver essas estruturas, você precisa aprender conhecimentos específicos relacionados à estrutura e bibliotecas de classes específicas da estrutura. Por exemplo, ao usar a máquina de eventos, para aproveitar os benefícios do não bloqueio, você deve evitar o uso de bibliotecas de classes síncronas e só pode usar as bibliotecas de classes assíncronas da máquina de eventos. Se você usar qualquer biblioteca de bloqueio (como a maioria da biblioteca padrão do Ruby), seu servidor perde sua escalabilidade ideal, porque o loop de eventos ainda será bloqueado constantemente, bloqueando o processamento dos eventos de E/S de tempos em tempos.
O Node foi originalmente projetado como uma plataforma de servidor de E/S não bloqueadora; portanto, em geral, você deve esperar que todo o código em execução não seja bloqueado. Como o JavaScript é muito pequeno e não force nenhum modelo de E/S (porque não possui uma biblioteca de classe de E/S padrão), o nó é construído em um ambiente muito puro e não haverá problemas herdados.
Como o nó e o JavaScript simplificam aplicativos assíncronos
O autor do Node, Ryan Dahl, usou inicialmente C para desenvolver esse projeto, mas descobriu que o contexto de manutenção de chamadas de função era muito complexo, resultando em alta complexidade de código. Então ele mudou para Lua, mas Lua já tem várias bibliotecas de E/S bloqueador. A mistura de bloqueio e não bloqueio pode confundir desenvolvedores e, assim, impedir que muitas pessoas construam aplicações escaláveis. Portanto, Lua também foi abandonado por Dahl. Finalmente, ele se voltou para o JavaScript, fechamentos em JavaScript e funções de objetos de primeiro nível, que tornam o JavaScript muito adequado para a programação orientada a eventos. A magia do JavaScript é uma das principais razões pelas quais o Node é tão popular.
O que é um fechamento
Um fechamento pode ser entendido como uma função especial, mas pode herdar e acessar variáveis no escopo em que é definido. Quando você passa uma função de retorno de chamada como um parâmetro para outra função, ela será chamada posteriormente. A mágica é que, quando essa função de retorno de chamada é chamada posteriormente, ela realmente se lembra do contexto em que se define e as variáveis no contexto dos pais e também pode acessá -las normalmente. Esse recurso poderoso é o núcleo do sucesso do Node.
O exemplo a seguir mostrará como os fechamentos de JavaScript funcionam em um navegador da web. Se você quiser ouvir um evento independente em um botão, pode fazer isso:
A cópia do código é a seguinte:
var clickCount = 0;
document.getElementById ('MyButton'). OnClick = function () {
clickCount += 1;
alerta ("clicou" + clickcount + "vezes");
};
É assim que quando estiver usando o jQuery:
A cópia do código é a seguinte:
var clickCount = 0;
$ ('botão#mybutton'). Clique (function () {
clickedCount ++;
alerta ('clicou' + clickCount + 'Times.');
});
No JavaScript, as funções são o primeiro tipo de objetos, o que significa que você pode passar as funções como parâmetros para outras funções. Nos dois exemplos acima, o primeiro atribui uma função a outra função, e a última passa a função como um parâmetro para outra função. A função de processamento de eventos de clique (função de retorno de chamada) pode acessar cada variável no bloco de código em que a função o define. Neste exemplo, ele pode acessar a variável ClickCount definida em seu fechamento dos pais.
A variável ClickCount está no escopo global (o escopo mais externo do JavaScript), que economiza o número de vezes que o usuário clica em um botão. Geralmente, é um mau hábito armazenar variáveis sob o escopo global, porque é fácil entrar em conflito com outro código e você deve colocar variáveis no escopo local onde as usa. Na maioria das vezes, apenas envolver o código com uma função é equivalente à criação de outro fechamento, que pode evitar facilmente poluir o ambiente global, exatamente assim:
A cópia do código é a seguinte:
(function () {
var clickCount = 0;
$ ('botão#mybutton'). Clique (function () {
ClickCount ++;
alerta ('clicou' + clickCount + 'Times.');
});
} ());
Nota: A sétima linha do código acima define uma função e a chama imediatamente. Este é um padrão de design comum no JavaScript: Crie um novo escopo criando uma função.
Como os fechamentos ajudam a programação assíncrona
No modelo de programação orientado a eventos, primeiro escreva o código a ser executado após a ocorrência do evento, depois coloque o código em uma função e, finalmente, passe a função como um parâmetro para o chamador e depois chamá-lo pela função do chamador posteriormente.
No JavaScript, uma função não é uma definição isolada. Também se lembra do contexto do escopo que é declarado. Esse mecanismo permite que as funções JavaScript acessem o contexto em que a definição de função está localizada e todas as variáveis no contexto pai.
Quando você passa uma função de retorno de chamada como um parâmetro para o chamador, a função será chamada em algum ponto posterior. Mesmo que o escopo que defina a função de retorno de chamada terminou, quando a função de retorno de chamada é chamada, ela ainda pode acessar todas as variáveis no escopo final e seu escopo pai. Como o último exemplo, a função de retorno de chamada é chamada dentro do clique () do jQuery, mas ainda pode acessar a variável ClickCount.
A magia dos fechamentos é mostrada anteriormente. A passagem de variáveis de estado para uma função permite executar programação orientada a eventos sem manter os estados. O mecanismo de fechamento da JavaScript ajudará você a mantê -los.
resumo
A programação orientada a eventos é um modelo de programação que determina o processo de execução do programa através do acionamento de eventos. Os programadores registram funções de retorno de chamada para eventos em que estão interessados (geralmente chamados de manipuladores de eventos), e o sistema chama o manipulador de eventos registrado quando o evento ocorre. Este modelo de programação tem muitas vantagens que os modelos de programação de bloqueio tradicionais não possuem. No passado, para implementar recursos semelhantes, deve ser usado com vários processos/threading multi-threading.
O JavaScript é um idioma poderoso devido ao seu primeiro tipo de função do objeto e propriedades de fechamento, tornando-o muito adequado para a programação orientada a eventos.