Introdução
As verificações do Garden Night já classificam corretamente as respostas, mas a versão inicial processa as tarefas uma por vez. Uma entrada lenta atrasa a chegada de outros resultados úteis. Você dividirá o trabalho entre um número pequeno e fixo de workers Go sem deixar de retornar cada resultado atribuído.
Esta unidade independente já inclui uma função completa para verificar um destino. É necessário conhecer funções, estruturas, slices, módulos, sintaxe básica de canais e os resultados HTTP da unidade anterior, ou ter experiência equivalente. Goroutines, fechamento de canais e conclusão dos workers são explicados antes do uso. Apenas destinos locais preparados são solicitados; esta é uma ferramenta pontual, não um benchmark nem uma plataforma de monitoramento. Reserve cerca de 60 minutos, incluindo leitura e erros comuns; é uma estimativa do autor.
Observe o trabalho real antes de distribuí-lo
Nesta etapa, execute o verificador sequencial atual e examine observações reais do serviço. Comece em Workspace. cd seleciona o projeto; export adiciona o diretório Go preparado ao caminho de busca de comandos deste shell. Repita as duas linhas em uma nova sessão.
cd /home/labex/project/site-checker
export PATH=/usr/local/go/bin:$PATH
Leia a função de verificação, o laço de trabalho e a lista sem timeouts. cat apenas exibe os arquivos:
cat check.go run.go overlap.json
Check já retorna ok, http_error, content_mismatch, connection_error ou timeout, mantém a identidade e o status recebido e fecha cada corpo. O Run sequencial ignora seu parâmetro workers. Esse é o comportamento que você mudará.
Cada entrada de overlap.json realiza trabalho preparado e finito de 250ms. Use dois segundos por destino para observar concorrência, e não timeouts do cliente. go run . executa este pacote; -input seleciona tarefas, -workers pede um máximo de verificações ativas e -timeout 2s define o orçamento. > escreve a saída padrão em um arquivo.
go run . -input overlap.json -workers 2 -timeout 2s > normal-report.json
Leia-o:
cat normal-report.json
As seis verificações devem ser ok. Esse relatório sozinho não mostra se elas se sobrepuseram. O observador preparado registra enter e exit quando um handler real do servidor começa e termina; active conta os handlers que estão realmente processando requisições. O pequeno leitor Python busca essas observações existentes sem adicionar requisições aos destinos:
python3 observe.py
Nesta nova execução sequencial, espere Actual handler peak: 1; entradas e saídas alternam, com no máximo um handler ativo. O observador estava pronto antes das requisições. O trabalho finito de 250ms oferece aos clientes concorrentes uma oportunidade de sobreposição; uma demora ou a duração total não provam, por si só, que ela ocorreu. Os eventos são a evidência.
A versão atual já produz um relatório completo, mas seu máximo de trabalho ativo ainda é um. Preserve esse relatório como exemplo normal deste passo. O próximo substitui apenas run.go; o serviço, as definições de destinos e a função de verificação continuam disponíveis.
Entregue tarefas a um número fixo de workers
Nesta etapa, projete como tarefas e resultados se movem e depois edite Run em Workspace. Mantenha Check concluída e as entradas preparadas inalteradas. Seu resultado é um pool funcional, não apenas várias goroutines iniciadas.
Decida quem é responsável por cada canal
Uma goroutine é uma função executada concorrentemente com quem a chamou; go func() { ... }() inicia uma função anônima imediatamente. Iniciá-la não prova que uma requisição HTTP está ativa. Um canal transfere valores tipados: make(chan Target) cria um canal de tarefas e make(chan Result) cria um canal de resultados. Nesses canais sem buffer, jobs <- target envia um valor e espera um receptor. for target := range jobs recebe até que o canal esteja fechado e vazio. close(jobs) significa que não serão enviados mais valores; valores já recebidos continuam válidos.
Antes de editar, decida qual parte do programa enviará tarefas, receberá tarefas, enviará resultados, receberá resultados e fechará cada canal. Deve haver um produtor de tarefas e um coletor de resultados. Workers recebem tarefas e enviam resultados. Somente o produtor sabe quando não serão enviadas mais tarefas, portanto ele fecha jobs. Um worker não pode fechar results com segurança enquanto outro ainda puder enviar.
sync.WaitGroup acompanha a conclusão. Uma declaração como var finished sync.WaitGroup começa em zero. Chame finished.Add(1) antes de iniciar cada worker; dentro dele, defer finished.Done() sinaliza sua saída. finished.Wait() espera até que todos os workers registrados tenham chamado Done. O canal de resultados só pode fechar depois que essa espera terminar. Dê essa responsabilidade de esperar e fechar a uma goroutine separada: se a goroutine principal esperasse antes de receber, os workers poderiam permanecer bloqueados ao enviar seus resultados. O produtor também executa separadamente para que a goroutine principal possa receber resultados enquanto o trabalho é atribuído.
Para iniciar o número solicitado de workers, um laço de contagem como for i := 0; i < workers; i++ { ... } inicializa i em zero, verifica o limite superior antes de cada iteração e incrementa i depois dela. Seu corpo inicia um worker a cada vez. Cada worker deve chamar Check de forma síncrona, enviar seu resultado retornado e só depois receber outra tarefa. Isso limita as verificações ativas do cliente ao número de workers. Não inicie uma nova goroutine para cada destino dentro do worker. Compartilhe um http.Client com Timeout: budget; esse cliente oferece suporte a requisições concorrentes. Somente o coletor acrescenta ao slice do relatório, para que os workers não disputem um slice ou map compartilhado. Diferentes ordens de conclusão são válidas.
Para uma lista de destinos vazia, o produtor ainda fecha jobs. Os workers então terminam, a goroutine responsável fecha results e o coletor pode retornar um slice vazio inicializado. Acompanhe essa sequência antes de escrever código. Explique também por que um destino com três workers deve terminar e por que vários destinos com um worker devem ser sequenciais.
Edite um laço de trabalho coerente
Faça backup do arquivo com uma extensão diferente de .go, para que Go não compile uma segunda definição de Run. cp copia o arquivo sem alterar o original:
cp run.go run.go.bak
Abra o arquivo existente:
nano run.go
Use as setas para se mover, digite para inserir texto e use Backspace ou Delete para remover caracteres. Ctrl significa manter Control pressionado enquanto pressiona a outra tecla. Ctrl+W, func Run, Enter pesquisa a função; Ctrl+C cancela uma busca. Ctrl+K recorta a linha atual, então use-o somente quando pretender remover essa linha. Salve com Ctrl+O, confirme run.go e depois Enter; Ctrl+X sai. Para descartar alterações indesejadas, pressione Ctrl+X e depois N quando o nano perguntar se deseja salvar. Em um prompt de nome de arquivo indesejado, Ctrl+C cancela esse prompt para que você possa corrigir a edição ou salvar no nome original.
Mantenha a assinatura da função e o cliente HTTP existente. Acrescente sync ao grupo de importações quando usar seu WaitGroup. Substitua o laço sequencial de destinos pelo seu projeto, atendendo a estas responsabilidades na ordem:
Primeiro, crie canais tipados de tarefas e resultados e um WaitGroup para a conclusão dos workers. Registre e inicie exatamente workers workers. Cada worker recebe até jobs ser fechado, envia cada resultado de Check e sinaliza sua conclusão ao retornar.
Depois, inicie um produtor separado que envia cada destino, incluindo URLs repetidas com IDs diferentes. Feche jobs após o último envio, inclusive quando não havia destinos.
Inicie também uma goroutine separada de conclusão que espera todos os workers e depois fecha results. Ela não deve fechar results enquanto um worker ainda puder enviar.
Por fim, na goroutine principal, inicialize um slice de resultados vazio, receba cada resultado até o canal de resultados fechar, acrescente cada um e retorne o slice.
Escreva as quatro partes antes de executar o programa. Um pool incompleto sem remetente, sem responsável pelo fechamento ou sem coletor não é um experimento intermediário útil. Você está decidindo como as partes se conectam; não há um Run pronto fornecido para colar. Os nomes de suas variáveis locais ou escolhas equivalentes de sincronização não são avaliados.
Salve e saia; depois examine seu arquivo real. cat o exibe sem alterá-lo:
cat run.go
gofmt -w formata o código-fonte no próprio local. Um erro de sintaxe identifica o arquivo e a linha: reabra run.go, corrija essa área, salve e repita a formatação. Não sobrescreva suas partes concluídas com uma nova resposta que substitua o arquivo inteiro.
gofmt -w run.go
Verifique trabalho real sobreposto
Agora execute o pool completo nas tarefas sem timeout com dois workers. O orçamento de dois segundos permite que o trabalho finito de 250ms termine normalmente:
go run . -input overlap.json -workers 2 -timeout 2s
Cada tarefa deve retornar um resultado ok. Leia os registros existentes do observador:
python3 observe.py
Neste cenário, o histórico acumulado agora deve mostrar Actual handler peak: 2, com um enter enquanto outro handler continua ativo. Se o pico permanecer em um, examine como os workers recebem e chamam Check; o tempo decorrido não prova sucesso. Uma demora incomum do escalonador pode tornar uma observação inconclusiva. Quando o histórico mais recente terminar com exit e active=0, as requisições finitas realmente terminaram; você pode repetir o lote inofensivo e ler seus novos eventos.
Um pico acumulado acima de dois significa que houve trabalho acima do limite em algum momento. Uma implementação anterior incorreta deixa esse pico mesmo depois de corrigir o código-fonte. Leia os eventos mais recentes de entrada e saída juntos e use Verify, que executa seu código-fonte original em um serviço de teste independente, para avaliar a implementação atual. Não limpe o histórico nem reinicie a VM para apagar um pico anterior.
Se o comando ficar preso em vez de retornar, Ctrl+C interrompe esta execução em primeiro plano; o serviço do evento preparado separadamente continua disponível. Reabra run.go e confira a cadeia de responsabilidades dos canais: todas as tarefas foram enviadas, o produtor fechou jobs, os workers chamam Done, o coletor consegue receber enquanto eles enviam e a goroutine responsável finalmente fecha results? Corrija localmente e formate. Antes de iniciar outra observação de concorrência, execute python3 observe.py e confirme que seu evento registrado mais recente é um exit com active=0. Se requisições finitas ainda estiverem ativas, leia novamente até que a condição registrada seja verdadeira; não use um sleep adivinhado nem apague observações. Timeouts do cliente não interrompem imediatamente o trabalho do servidor.
Se um arquivo estiver ausente, use pwd e o cd da primeira etapa para recuperar o diretório. Se go não estiver disponível em um novo shell, repita o export de sessão daquela etapa. Nem uma edição comum nem um erro de caminho exigem alterar arquivos globais do shell ou reiniciar a VM.
Conclua cada tarefa dentro de seu orçamento
Nesta etapa, use o pool no relatório do evento e teste seus limites. Leia as tarefas reais antes da execução:
cat targets.json
Os tipos esperados são dois ok, um http_error com status 403, um content_mismatch com 200, um timeout com zero e um connection_error com zero. O serviço de ingressos tem demora finita de 600ms; o orçamento do cliente de 200ms encerra intencionalmente a verificação mais cedo. Os dois IDs da URL de credenciamento repetida devem continuar no relatório.
Salve um novo relatório com dois workers. > substitui apenas report.json; os erros continuam visíveis no terminal.
go run . -input targets.json -workers 2 -timeout 200ms > report.json
Leia o arquivo realmente salvo:
cat report.json
A ordem das linhas pode diferir da entrada e variar entre execuções. Relacione por ID: uma requisição lenta pode terminar depois de outra rápida atribuída mais tarde. Um worker não inicia o próximo destino antes de Check retornar, mas um handler do servidor pode continuar trabalho finito depois que seu cliente esgotou o tempo e foi embora. Por isso o curso mede o limite de workers em requisições separadas sem timeout; não confunde handlers que continuam trabalhando com um defeito no pool.
O pool também deve atender menos tarefas que workers ou nenhuma tarefa. Estes comandos usam listas preparadas e mostram a saída diretamente, mantendo report.json intacto:
go run . -input single.json -workers 3 -timeout 2s
go run . -input empty.json -workers 3 -timeout 2s
Espere um resultado ok, depois [], com ambos os comandos terminando. Por fim, peça um limite de um para vários destinos sem timeout:
go run . -input overlap.json -workers 1 -timeout 2s
Todos os resultados continuam ok. Essa chamada deve ser sequencial mesmo que seu código consiga iniciar vários workers. O pico acumulado preserva atividade anterior e não permite isolar o limite dessa chamada. A verificação do curso cria um novo serviço independente para cada orçamento, incluindo um, dois e três. Para várias tarefas com orçamento dois ou três, exige sobreposição real de pelo menos duas sem ultrapassar o máximo solicitado; não exige um pico exatamente igual a três. Lotes vazios e destinos únicos não têm exigência de sobreposição.
Preserve o relatório completo, as entradas e o serviço originais. Agora você tem um comando reutilizável que combina trabalho ativo limitado com resultados completos, inclusive falhas relatadas com honestidade.
Resumo
Você usou canais de tarefas e resultados com responsabilidade explícita de fechamento, um número fixo de workers e sincronização de conclusão. Um único coletor mantém todos os resultados sem disputas para modificar o slice compartilhado. Eventos reais do servidor distinguem trabalho sequencial, concorrência limitada e trabalho excessivo; testes de timeout separados evitam confundir trabalho remanescente do servidor com violação do limite de workers. O relatório continua útil mesmo quando algumas entradas falham.



