Saiba mais sobre goroutines
Simultaneidade é a composição de atividades independentes, como o trabalho que um servidor web faz quando lida com várias solicitações de usuários simultaneamente, mas de forma autônoma. A simultaneidade está presente em muitos programas atualmente. Os servidores Web são um exemplo, mas você também vê a necessidade de simultaneidade no processamento de quantidades significativas de dados em lotes.
Go tem dois estilos para escrever programas simultâneos. Um é o estilo tradicional que você pode ter usado em outros idiomas com threads. Neste módulo, você aprenderá sobre o estilo do Go, onde os valores são passados entre atividades independentes conhecidas como goroutines para comunicar processos.
Se você estiver aprendendo sobre simultaneidade pela primeira vez, recomendamos que você passe algum tempo extra revisando cada parte do código que escreveremos para praticar.
A abordagem de Go à simultaneidade
Normalmente, o maior problema na escrita de programas simultâneos é o compartilhamento de dados entre processos. Go tem uma abordagem diferente de outras linguagens de programação com comunicação, porque Go passa dados de um lado para o outro através de canais. Essa abordagem significa que apenas uma atividade (goroutine) tem acesso aos dados, e não há nenhuma condição de corrida por design. À medida que você aprende sobre goroutines e canais neste módulo, você entenderá melhor a abordagem de simultaneidade do Go.
A abordagem de Go pode ser resumida no seguinte slogan: "Não se comunique compartilhando memória, em vez disso, compartilhe memória comunicando". Abordaremos essa abordagem nas seções a seguir, mas você também pode saber mais na postagem do Go Blog Share Memory By Comunicaating.
Como mencionamos anteriormente, Go também inclui primitivos de simultaneidade de baixo nível. Mas abordaremos apenas a abordagem idiomática de Go para simultaneidade neste módulo.
Vamos começar explorando goroutines.
Gorotinas
Um goroutine é uma atividade simultânea em um thread leve, não o tradicional que você tem em um sistema operacional. Vamos supor que você tenha um programa que grava na saída e outra função que calcula coisas como adicionar dois números. Um programa simultâneo pode ter várias goroutines chamando ambas as funções ao mesmo tempo.
Podemos dizer que a primeira rotina que um programa executa é a main()
função. Se você quiser criar outro goroutine, você tem que usar a go
palavra-chave antes de chamar a função, como este exemplo:
func main(){
login()
go launch()
}
Como alternativa, você descobrirá que muitos programas gostam de usar funções anônimas para criar goroutines, como neste código:
func main(){
login()
go func() {
launch()
}()
}
Para ver goroutines em ação, vamos escrever um programa simultâneo.
Escrever um programa simultâneo
Como queremos nos concentrar apenas na parte simultânea, vamos usar um programa existente que verifique se um ponto de extremidade da API está respondendo ou não. Aqui está o código:
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
start := time.Now()
apis := []string{
"https://management.azure.com",
"https://dev.azure.com",
"https://api.github.com",
"https://outlook.office.com/",
"https://api.somewhereintheinternet.com/",
"https://graph.microsoft.com",
}
for _, api := range apis {
_, err := http.Get(api)
if err != nil {
fmt.Printf("ERROR: %s is down!\n", api)
continue
}
fmt.Printf("SUCCESS: %s is up and running!\n", api)
}
elapsed := time.Since(start)
fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}
Quando você executa o código anterior, você obtém a seguinte saída:
SUCCESS: https://management.azure.com is up and running!
SUCCESS: https://dev.azure.com is up and running!
SUCCESS: https://api.github.com is up and running!
SUCCESS: https://outlook.office.com/ is up and running!
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://graph.microsoft.com is up and running!
Done! It took 1.658436834 seconds!
Nada é fora do normal aqui, mas podemos fazer melhor. Talvez possamos verificar todos os sites ao mesmo tempo? Em vez de levar quase dois segundos, o programa poderia terminar em menos de 500 ms.
Observe que a parte do código que precisamos executar simultaneamente é a que faz a chamada HTTP para o site. Em outras palavras, precisamos criar uma rotina para cada API que o programa está verificando.
Para criar um goroutine, precisamos usar a go
palavra-chave antes de chamar uma função. Mas não temos uma função lá. Vamos refatorar esse código e criar uma nova função, como esta:
func checkAPI(api string) {
_, err := http.Get(api)
if err != nil {
fmt.Printf("ERROR: %s is down!\n", api)
return
}
fmt.Printf("SUCCESS: %s is up and running!\n", api)
}
Observe que não precisamos mais da continue
palavra-chave porque não estamos em um for
loop. Para interromper o fluxo de execução da função, usamos a return
palavra-chave. Agora, precisamos modificar o main()
código na função para criar um goroutine por API, assim:
for _, api := range apis {
go checkAPI(api)
}
Execute novamente o programa e veja o que acontece.
Parece que o programa não está mais verificando as APIs, certo? Você pode ver algo como a seguinte saída:
Done! It took 1.506e-05 seconds!
Isso foi rápido! O que aconteceu? Você vê a mensagem final dizendo que o programa terminou porque Go criou uma rotina para cada site dentro do loop, e foi imediatamente para a próxima linha.
Mesmo que não pareça que a checkAPI
função está em execução, ela está em execução. Simplesmente não teve tempo de terminar. Observe o que acontece se você incluir um temporizador de suspensão logo após o loop, assim:
for _, api := range apis {
go checkAPI(api)
}
time.Sleep(3 * time.Second)
Agora, quando você executar novamente o programa, você pode ver uma saída como esta:
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://api.github.com is up and running!
SUCCESS: https://management.azure.com is up and running!
SUCCESS: https://dev.azure.com is up and running!
SUCCESS: https://outlook.office.com/ is up and running!
SUCCESS: https://graph.microsoft.com is up and running!
Done! It took 3.002114573 seconds!
Parece que está funcionando, certo? Bem, não precisamente. E se você quiser adicionar um novo site à lista? Talvez três segundos não sejam suficientes. Como você saberia? Não pode fazê-lo. Tem que haver uma maneira melhor, e é isso que discutiremos na próxima seção quando falarmos sobre canais.