VerificaWebsites
, que verifica o status de uma lista de URLs.true
para uma boa resposta, false
para uma resposta ruim.VerificadorWebsite
como parâmetro, que leva um URL e retorna um boleano. Isso é usado pela função que verifica todos os websites.VerificaWebsites
para que possamos ver o efeito das nossas alterações.VerificaWebsites
usando um slice de 100 URLs e usa uma nova implementação falsa de VerificadorWebsite
. slowStubVerificadorWebsite
é intencionalmente lento. Ele usa um time.Sleep
para esperar exatamente 20 milissegundos e então retorna verdadeiro.go test -bench=.
(ou, se estiver no Powershell do Windows, go test -bench="."
):VerificaWebsites
teve uma marca de 2249228637 nanosegundos - pouco mais de dois segundos.VerificaWebsites
mais rápido. Ao invés de esperar por um website responder antes de enviar uma requisição para o próximo website, vamos dizer para nosso computador fazer a próxima requisição enquanto espera pela primeira.fazAlgumaCoisa()
, esperamos que ela retorne alguma coisa (mesmo se não tiver valor para retornar, ainda esperamos que ela termine). Chamamos essa operação de bloqueante - espera algo acabar para terminar seu trabalho. Uma operação que não bloqueia no Go vai rodar em um processo separado, chamado de goroutine. Pense no processo como uma leitura de uma página de código Go de cima para baixo, 'entrando' em cada função quando é chamado para ler o que essa página faz. Quando um processo separado começa, é como se outro leitor começasse a ler o interior da função, deixando o leitor original continuar lendo a página.go
colocando a palavra-chave go
na frente da função: go fazAlgumaCoisa()
.go
na frente da chamada de função, costumamos usar funções anônimas quando queremos iniciar uma goroutine. Uma função anônima literal é bem parecida com uma declaração de função normal, mas (obviamente) sem um nome. Você pode ver uma acima no corpo do laço for
.()
no final da função anônima. Em segundo lugar, elas mantém acesso ao escopo léxico em que são definidas - todas as variáveis que estão disponíveis no ponto em que a função anônima é declarada também estão disponíveis no corpo da função.VerificadorWebsite
), e cada uma vai adicionar seu resultado ao map de resultados.VerificadorWebsite
agora estão devolvendo um map vazio. O que deu de errado?for
iniciou teve tempo de adicionar seu resultado ao map resultados
; a função VerificadorWebsite
é rápida demais para eles, e por isso retorna o map vazio.url
é reutilizada para cada iteração do laço for
- ele recebe um valor novo de urls
a cada vez. Mas cada uma das goroutines tem uma referência para a variável url
- eles não têm sua própria cópia independente. Logo, todas estão escrevendo o valor que url
tem no final da iteração - o último URL. E é por isso que o resultado que obtemos é a última URL.u
- e chamar a função anônima com url
como argumento, nos certificamos de que o valor de u
está fixado como o valor de url
para cada iteração do laço de url
e não pode ser modificado.fatal error: concurrent map writes
(erro fatal: escrita concorrente no map). Às vezes, quando executamos nossos testes, duas das goroutines escrevem no map resultados
ao mesmo tempo. Maps em Go não gostam quando mais de uma coisa tenta escrever algo neles ao mesmo tempo, então o erro fatal
é gerado.resultados
, ficamos vulneráveis à situação de duas goroutines escreverem nele ao mesmo tempo.race
: go test -race
.WARNING: DATA RACE
(CUIDADO: CONDIÇÃO DE CORRIDA) é bem claro. Lendo o corpo do erro podemos ver duas goroutines diferentes performando escritas em um map:Write at 0x00c000120089 by goroutine 6:
Previous write at 0x00c000120089 by goroutine 8:
/home/larien/go/src/github.com/larien/aprenda-go-com-testes/concorrencia/v2/VerificaWebsites.go:17 +0x97
/home/larien/go/src/github.com/larien/aprenda-go-com-testes/concorrencia/v2/VerificaWebsites.go:16 +0xb2
VerificadorWebsite
com a URL.resultados
, agora temos um canalResultados
, que criamos da mesma forma usando make
. O chan resultado
é o tipo do canal - um canal de resultado
. O tipo novo, resultado
, foi criado para associar o retorno de VerificadorWebsite
com a URL sendo verificada - é uma estrutura que contém uma string
e um bool
. Já que não precisamos que nenhum valor tenha um nome, cada um deles é anônimo dentro da struct; isso pode ser útil quando for difícil saber que nome dar a um valor.map
diretamente, enviamos uma struct resultado
para cada chamada de vw
para o canalResultado
com uma sintaxe de envio. Essa sintaxe usa o operador <-
, usando um canal à esquerda e um valor à direita:for
itera uma vez sobre cada uma das URLs. Dentro, estamos usando uma expressão de recebimento, que atribui um valor recebido por um canal a uma variável. Essa expressão também usa o operador <-
, mas com os dois operandos ao posições invertidas: o canal agora fica à direita e a variável que está recebendo o valor dele fica à esquerda:resultado
recebido para atualizar o map.resultados
, garantindo que só aconteça uma por vez. Apesar de cada uma das chamadas de vw
e cada envio ao canal resultado estar acontecendo em paralelo dentro de seu próprio processo, cada resultado está sendo resolvido de cada vez enquanto tiramos o valor do canal resultado com a expressão recebida.VerificaWebsites
; as entradas e saídas não mudaram, ela apenas ficou mais rápida. Mas, com os testes que já tinhamos escrito, assim como com o benchmark que escrevemos, fomos capazes de refatorar o VerificaWebsites
de forma que mantivéssemos a confiança de que o software ainda estava funcionando, enquanto demonstramos que ela realmente havia ficado mais rápida.