Eu pesquisei na web alguns detalhes técnicos sobre bloqueio de E / S e E / S sem bloqueio e encontrei várias pessoas afirmando que E / S sem bloqueio seria mais rápido do que E / S de bloqueio. Por exemplo, neste documento .
Se eu usar o bloqueio de E / S, é claro que o thread que está bloqueado no momento não pode fazer mais nada ... Porque está bloqueado. Mas assim que um thread começa a ser bloqueado, o sistema operacional pode alternar para outro thread e não voltar até que haja algo a ser feito para o thread bloqueado. Portanto, enquanto houver outro thread no sistema que precisa da CPU e não está bloqueado, não deve haver mais tempo ocioso da CPU em comparação com uma abordagem sem bloqueio baseada em eventos, certo?
Além de reduzir o tempo de ociosidade da CPU, vejo mais uma opção para aumentar o número de tarefas que um computador pode realizar em um determinado intervalo de tempo: Reduzir a sobrecarga introduzida pela troca de threads. Mas como isso pode ser feito? E a sobrecarga é grande o suficiente para mostrar efeitos mensuráveis? Aqui está uma ideia de como posso imaginá-lo funcionando:
- Para carregar o conteúdo de um arquivo, um aplicativo delega essa tarefa a uma estrutura de i / o baseada em eventos, passando uma função de retorno de chamada junto com um nome de arquivo
- A estrutura do evento delega ao sistema operacional, que programa um controlador DMA do disco rígido para gravar o arquivo diretamente na memória
- A estrutura de eventos permite que mais códigos sejam executados.
- Após a conclusão da cópia do disco para a memória, o controlador DMA causa uma interrupção.
- O manipulador de interrupção do sistema operacional notifica a estrutura de E / S baseada em eventos sobre o arquivo sendo completamente carregado na memória. Como isso faz? Usando um sinal ??
- O código que é executado atualmente na estrutura de e / s de evento termina.
- A estrutura de i / o baseada em eventos verifica sua fila e vê a mensagem do sistema operacional da etapa 5 e executa o retorno de chamada que obteve na etapa 1.
É assim que funciona? Se não, como funciona? Isso significa que o sistema de eventos pode funcionar sem nunca ter a necessidade de tocar explicitamente na pilha (como um agendador real que precisaria fazer backup da pilha e copiar a pilha de outro encadeamento na memória enquanto alterna os encadeamentos)? Quanto tempo isso realmente economiza? Existe mais do que isso?
fonte
Respostas:
A maior vantagem de E / S não bloqueante ou assíncrona é que seu thread pode continuar seu trabalho em paralelo. Claro que você também pode conseguir isso usando um thread adicional. Como você declarou para melhor desempenho geral (sistema), acho que seria melhor usar E / S assíncrona e não vários threads (reduzindo assim a troca de threads).
Vejamos as possíveis implementações de um programa de servidor de rede que deve lidar com 1000 clientes conectados em paralelo:
Cada thread requer recursos de memória (também memória kernel!), Que é uma desvantagem. E cada thread adicional significa mais trabalho para o planejador.
Isso retira carga do sistema porque temos menos threads. Mas também impede que você use todo o desempenho de sua máquina, porque você pode acabar levando um processador a 100% e deixando todos os outros processadores ociosos.
Isso retira carga do sistema porque há menos threads. E pode usar todos os processadores disponíveis. No Windows, essa abordagem é suportada pela API Thread Pool .
É claro que ter mais threads não é um problema per se. Como você deve ter percebido, escolhi um número bastante alto de conexões / threads. Duvido que você veja qualquer diferença entre as três implementações possíveis se estivermos falando sobre apenas uma dúzia de threads (isso também é o que Raymond Chen sugere na postagem do blog do MSDN O Windows tem um limite de 2.000 threads por processo? ).
No Windows, o uso de E / S de arquivo sem buffer significa que as gravações devem ter um tamanho múltiplo do tamanho da página. Eu não testei, mas parece que isso também pode afetar o desempenho de gravação positivamente para gravações síncronas e assíncronas em buffer.
As etapas de 1 a 7 que você descreve dão uma boa ideia de como isso funciona. No Windows, o sistema operacional irá informá-lo sobre a conclusão de uma E / S assíncrona (
WriteFile
comOVERLAPPED
estrutura) usando um evento ou um retorno de chamada. As funções de retorno de chamada só serão chamadas, por exemplo, quando seu código chamarWaitForMultipleObjectsEx
combAlertable
definido comotrue
.Mais algumas leituras na web:
fonte
E / S inclui vários tipos de operações, como leitura e gravação de dados de discos rígidos, acesso a recursos de rede, chamada de serviços da web ou recuperação de dados de bancos de dados. Dependendo da plataforma e do tipo de operação, a E / S assíncrona geralmente tirará proveito de qualquer hardware ou suporte de sistema de baixo nível para realizar a operação. Isso significa que será executado com o menor impacto possível na CPU.
No nível do aplicativo, a E / S assíncrona evita que os threads tenham que esperar a conclusão das operações de E / S. Assim que uma operação de E / S assíncrona é iniciada, ela libera o encadeamento no qual foi iniciada e um retorno de chamada é registrado. Quando a operação é concluída, o retorno de chamada é enfileirado para execução no primeiro encadeamento disponível.
Se a operação de E / S for executada de forma síncrona, ele mantém seu thread em execução sem fazer nada até que a operação seja concluída. O tempo de execução não sabe quando a operação de E / S é concluída, portanto, ele fornecerá periodicamente algum tempo de CPU para o encadeamento em espera, tempo de CPU que poderia ser usado por outros encadeamentos que têm operações reais vinculadas à CPU para executar.
Portanto, como @ user1629468 mencionou, a E / S assíncrona não oferece melhor desempenho, mas melhor escalabilidade. Isso é óbvio ao executar em contextos que têm um número limitado de threads disponíveis, como é o caso com aplicativos da web. Os aplicativos da Web geralmente usam um pool de threads a partir do qual atribuem threads a cada solicitação. Se as solicitações forem bloqueadas em operações de E / S de longa execução, há o risco de esgotar o pool da web e fazer com que o aplicativo da web congele ou demore para responder.
Uma coisa que percebi é que a E / S assíncrona não é a melhor opção ao lidar com operações de E / S muito rápidas. Nesse caso, o benefício de não manter um encadeamento ocupado enquanto espera pela conclusão da operação de E / S não é muito importante e o fato de a operação ser iniciada em um encadeamento e concluída em outro adiciona uma sobrecarga à execução geral.
Você pode ler uma pesquisa mais detalhada que fiz recentemente sobre o tópico de E / S assíncrona vs. multithreading aqui .
fonte
O principal motivo para usar AIO é a escalabilidade. Quando visto no contexto de alguns tópicos, os benefícios não são óbvios. Mas quando o sistema é dimensionado para 1000 threads, o AIO oferece um desempenho muito melhor. A ressalva é que a biblioteca AIO não deve apresentar mais gargalos.
fonte
Para presumir uma melhoria de velocidade devido a qualquer forma de multi-computação, você deve presumir que várias tarefas baseadas em CPU estão sendo executadas simultaneamente em vários recursos de computação (geralmente núcleos de processador) ou então que nem todas as tarefas dependem do uso simultâneo de o mesmo recurso - ou seja, algumas tarefas podem depender de um subcomponente do sistema (armazenamento em disco, por exemplo), enquanto algumas tarefas dependem de outro (receber comunicação de um dispositivo periférico) e outras ainda podem exigir o uso de núcleos de processador.
O primeiro cenário é freqüentemente conhecido como programação "paralela". O segundo cenário é frequentemente referido como programação "simultânea" ou "assíncrona", embora "simultâneo" às vezes também seja usado para se referir ao caso de meramente permitir que um sistema operacional intercale a execução de várias tarefas, independentemente de tal execução ter coloque em série ou se vários recursos puderem ser usados para alcançar a execução paralela. Neste último caso, "simultâneo" geralmente se refere à maneira como a execução é escrita no programa, em vez da perspectiva da simultaneidade real da execução da tarefa.
É muito fácil falar sobre tudo isso com suposições tácitas. Por exemplo, alguns são rápidos em fazer uma afirmação como "E / S assíncrona será mais rápida do que E / S multiencadeada". Essa afirmação é duvidosa por vários motivos. Em primeiro lugar, pode ser o caso de que algum determinado framework de E / S assíncrono seja implementado precisamente com multi-threading, caso em que eles são um no mesmo e não faz sentido dizer que um conceito "é mais rápido que" o outro .
Em segundo lugar, mesmo no caso em que há uma implementação de thread único de uma estrutura assíncrona (como um loop de evento de thread único), você ainda deve fazer uma suposição sobre o que esse loop está fazendo. Por exemplo, uma coisa boba que você pode fazer com um loop de evento de thread único é solicitar que ele conclua de forma assíncrona duas tarefas diferentes exclusivamente relacionadas à CPU. Se você fizesse isso em uma máquina com apenas um único núcleo de processador idealizado (ignorando as otimizações de hardware modernas), executar essa tarefa "de forma assíncrona" não teria um desempenho diferente do que executá-la com dois threads gerenciados independentemente ou apenas com um único processo - - a diferença pode se resumir à troca de contexto de thread ou otimizações de agendamento do sistema operacional, mas se ambas as tarefas forem para a CPU, serão semelhantes em ambos os casos.
É útil imaginar muitos dos casos incomuns ou estúpidos que você pode encontrar.
"Assíncrono" não precisa ser simultâneo, por exemplo, como acima: você executa "de forma assíncrona" duas tarefas associadas à CPU em uma máquina com exatamente um núcleo de processador.
A execução multi-threaded não precisa ser simultânea: você gera duas threads em uma máquina com um único núcleo de processador ou pede a duas threads para adquirir qualquer outro tipo de recurso escasso (imagine, digamos, um banco de dados de rede que só pode estabelecer um conexão de cada vez). A execução dos threads pode ser intercalada, no entanto, o planejador do sistema operacional achar necessário, mas seu tempo de execução total não pode ser reduzido (e será aumentado a partir da troca de contexto do thread) em um único núcleo (ou mais geralmente, se você gerar mais threads do que o existente núcleos para executá-los ou ter mais threads pedindo um recurso do que o recurso pode sustentar). A mesma coisa também se aplica ao multiprocessamento.
Portanto, nem E / S assíncrona nem multi-threading oferecem qualquer ganho de desempenho em termos de tempo de execução. Eles podem até mesmo desacelerar as coisas.
Se você definir um caso de uso específico, no entanto, como um programa específico que faz uma chamada de rede para recuperar dados de um recurso conectado à rede, como um banco de dados remoto, e também faz alguns cálculos ligados à CPU local, você pode começar a raciocinar sobre as diferenças de desempenho entre os dois métodos, considerando uma suposição particular sobre o hardware.
As perguntas a serem feitas: Quantas etapas computacionais eu preciso executar e quantos sistemas independentes de recursos existem para executá-las? Existem subconjuntos das etapas computacionais que requerem o uso de subcomponentes de sistema independentes e podem se beneficiar ao fazer isso simultaneamente? Quantos núcleos de processador eu tenho e qual é a sobrecarga de usar vários processadores ou threads para concluir tarefas em núcleos separados?
Se suas tarefas dependem amplamente de subsistemas independentes, uma solução assíncrona pode ser boa. Se o número de threads necessários para lidar com isso fosse grande, de forma que a troca de contexto se tornasse não trivial para o sistema operacional, uma solução assíncrona de thread único seria melhor.
Sempre que as tarefas são vinculadas ao mesmo recurso (por exemplo, várias necessidades para acessar simultaneamente a mesma rede ou recurso local), o multi-threading provavelmente irá introduzir sobrecarga insatisfatória, e enquanto a assincronia de thread único pode introduzir menos sobrecarga, em tal recurso- situação limitada, também não pode produzir uma aceleração. Nesse caso, a única opção (se você quiser uma aceleração) é fazer várias cópias desse recurso disponíveis (por exemplo, vários núcleos de processador se o recurso escasso for CPU; um banco de dados melhor que suporte mais conexões simultâneas se o recurso escasso é um banco de dados com conexão limitada, etc.).
Outra maneira de colocar isso é: permitir que o sistema operacional intercale o uso de um único recurso para duas tarefas não pode ser mais rápido do que simplesmente deixar uma tarefa usar o recurso enquanto a outra espera, então deixar a segunda tarefa terminar em série. Além disso, o custo do programador de intercalação significa que, em qualquer situação real, ele realmente cria uma desaceleração. Não importa se o uso intercalado ocorre da CPU, um recurso de rede, um recurso de memória, um dispositivo periférico ou qualquer outro recurso do sistema.
fonte
Uma possível implementação de E / S sem bloqueio é exatamente o que você disse, com um pool de threads de segundo plano que bloqueiam E / S e notificam a thread do originador da E / S por meio de algum mecanismo de retorno de chamada. Na verdade, é assim que funciona o módulo AIO na glibc. Aqui estão alguns detalhes vagos sobre a implementação.
Embora esta seja uma boa solução que é bastante portátil (contanto que você tenha threads), o sistema operacional normalmente é capaz de atender a E / S sem bloqueio com mais eficiência. Este artigo da Wikipedia lista as possíveis implementações além do pool de threads.
fonte
Atualmente, estou no processo de implementação do async io em uma plataforma embarcada usando protothreads. O io sem bloqueio faz a diferença entre rodar a 16000fps e 160fps. O maior benefício de não bloquear o io é que você pode estruturar seu código para fazer outras coisas enquanto o hardware faz seu trabalho. Mesmo a inicialização de dispositivos pode ser feita em paralelo.
Martin
fonte
No Node, vários threads estão sendo iniciados, mas é uma camada inferior no tempo de execução do C ++.
https://codeburst.io/how-node-js-single-thread-mechanism-work-understanding-event-loop-in-nodejs-230f7440b0ea
https://itnext.io/multi-threading-and-multi-process-in-node-js-ffa5bb5cde98
A explicação "Node é mais rápido porque não bloqueia ..." é um pouco de marketing e essa é uma ótima pergunta. É eficiente e escalonável, mas não exatamente de thread único.
fonte
A melhoria, tanto quanto eu sei é que usos Asynchronous I / O (Estou falando de MS Sistema, só para esclarecer) o assim chamado portas de conclusão de E / S . Usando a chamada assíncrona, a estrutura potencializa essa arquitetura automaticamente, e isso deve ser muito mais eficiente do que o mecanismo de threading padrão. Como experiência pessoal, posso dizer que você sentiria sensivelmente seu aplicativo mais reativo se preferisse AsyncCalls em vez de bloquear threads.
fonte
Deixe-me dar um contra-exemplo de que a E / S assíncrona não funciona. Estou escrevendo um proxy semelhante ao abaixo - usando boost :: asio. https://github.com/ArashPartow/proxy/blob/master/tcpproxy_server.cpp
No entanto, o cenário do meu caso é que as mensagens de entrada (do lado dos clientes) são rápidas, enquanto as mensagens de saída (para o servidor) são lentas para uma sessão, para acompanhar a velocidade de entrada ou para maximizar a taxa de transferência total do proxy, temos que usar várias sessões em uma conexão.
Portanto, essa estrutura de E / S assíncrona não funciona mais. Precisamos de um pool de threads para enviar ao servidor, atribuindo a cada thread uma sessão.
fonte