Olá, eu sou o instrutor Guilherme Lima e estou feliz que você queira aprender mais sobre concorrência aplicada na linguagem Go.
Audiodescrição: Guilherme se identifica como um homem branco. Possui cabelos curtos e pretos, além de bigode e barba rentes ao rosto, na mesma cor. No rosto, usa óculos de armação preta. No corpo, veste uma camisa azul clara com gola. Ao fundo, uma parede lisa com um quadro de desenho geométrico pendurado é iluminada em tons de verde e azul.
Neste curso, vamos criar um projeto do zero, inspirado no mundo real, onde temos uma aplicação que busca preços de diferentes sites. Como nosso objetivo é a concorrência, não vamos criar o scrap (raspagem de dados) para realizar, de fato, a busca desses preços. Vamos simular essa busca e desenvolver uma aplicação que busca em cada um dos sites de maneira sequencial.
Para o cenário acima, perceberemos que podemos usar concorrência para obter um ganho de performance considerável. Vamos entender também:
Por questões de tempo, não abordaremos neste curso os tópicos de Atomics, Select, Mutex e outros assuntos que também envolvem concorrência com Go. O assunto de concorrência é muito amplo e extenso. Portanto, decidimos falar de maneira profunda sobre canais e desenvolver um projeto prático que você construirá do zero, aplicará concorrência e verá o ganho de performance que obtemos ao utilizá-la na aplicação.
Sabemos que a concorrência não resolverá todos os problemas de código, mas temos certeza de que ela pode melhorar bastante a performance em situações que enfrentamos no mundo real.
Vamos iniciar nossos estudos sobre concorrência em Go. Para começar, não teremos um projeto base com várias coisas já feitas. Vamos começar do zero, focando exclusivamente em concorrência.
Para isso, criaremos um projeto vazio. Na área de trabalho do computador, criaremos uma nova pasta chamada "buscador". A ideia é criar uma aplicação que simula a busca de preços de produtos em diferentes e-commerces e plataformas.
Utilizaremos o Visual Studio Code para editar nosso código. Ao abri-lo, pressionaremos "Ctrl + J" para abrir o terminal e iniciaremos o projeto com o comando abaixo, onde buscador
será o nome do projeto:
go mod init buscador
O VS Code abrirá um novo arquivo chamado go.mod
, dentro do qual nos informará o nome do nosso módulo e a versão do Go que estamos utilizando.
go.mod
:
module buscador
go 1.22.5
Podemos fechar esse arquivo, clicando no "X" à direita do seu nome, na parte superior do programa.
Vamos criar a estrutura da nossa aplicação. Acessando a aba do explorador de arquivos, na lateral esquerda, clicaremos no botão "New Folder" — representado por uma pasta e o símbolo de "+" — para criar uma pasta chamada "cmd". Mantendo a pasta selecionada, clicaremos no botão "New File" — representado por uma folha e o símbolo de "+" — para criar um arquivo chamado main.go
.
Esses conceitos já foram abordados em cursos anteriores de Go.
Acessando o arquivo main.go
, adicionaremos o pacote main
e a função main
por meio do atalho pkgm
.
pkmg
main.go
:
package main
func main() {
}
Este é o ponto de partida de toda a nossa aplicação.
Voltando ao explorador lateral, criaremos uma pasta chamada "internal" no mesmo nível da "cmd". Dentro dela, criaremos outra pasta chamada "fetcher" para manter todo o código relacionado à busca de preços das outras aplicações. Dentro da pasta "Fetcher", criaremos um arquivo chamado price_fetcher.go
.
Acessando esse arquivo, informaremos o pacote fetcher
.
price_fetcher.go
:
package fetcher
A ideia agora é simular essa busca. Não iremos, de fato, acessar lojas para consultar preços, pois o curso é sobre concorrência, não sobre scraping. Queremos simular essas buscas e pensar em quanto tempo elas levariam para aplicar concorrência de forma prática.
Nosso objetivo principal nesta função é buscar preços de diferentes sites. Criaremos uma função que simula a busca do primeiro site, chamada FetchPriceFromSite1()
. Ao realizar a busca, a informação mais importante é o preço, que será retornado como float64
.
package fetcher
func FetchPricesFromSite1() float64 {
}
Entre as chaves da função, simularemos a autenticação, o acesso, busca e retorno da API, e depois coletaremos o preço. Isso levará um tempo, que simularemos com time.Sleep()
, demorando um segundo no site 1. Após um segundo, retornaremos um valor aleatório rand.Float64() * 100
.
func FetchPriceFromSite1() float64 {
time.Sleep(1 * time.Second)
return rand.Float64() * 100
}
Cada e-commerce pode levar um tempo diferente para retornar. Vamos criar funções semelhantes para os sites e 3, alterando o tempo de espera para 3 e 2 segundos, respectivamente.
func FetchPriceFromSite2() float64 {
time.Sleep(3 * time.Second)
return rand.Float64() * 100
}
func FetchPriceFromSite3() float64 {
time.Sleep(2 * time.Second)
return rand.Float64() * 100
}
main
Temos três funções, cada uma levando um tempo diferente para retornar um valor. Nosso próximo desafio é usar essas funções na função main()
.
Acessando o arquivo main.go
, entre as chaves da função main()
, criaremos e inicializaremos as variáveis price1
, price2
e price3
para armazenar os preços retornados de suas respectivas funções.
Em seguida, exibiremos esses valores na tela usando fmt.Printf()
, formatando para duas casas decimais e pulando uma linha entre cada preço para melhor visualização, por meio do comando "R$ %.2f \n"
:
main.go
:
package main
func main() {
price1 := fetcher.FetchPriceFromSite1()
price2 := fetcher.FetchPriceFromSite2()
price3 := fetcher.FetchPriceFromSite3()
fmt.Printf("R$ %.2f \n", price1)
fmt.Printf("R$ %.2f \n", price2)
fmt.Printf("R$ %.2f \n", price3)
}
Executaremos o programa no terminal com o comando abaixo:
go run cmd/main.go
Ele buscará o preço de cada site e exibirá os resultados.
R$ 59.42
R$ 55.45
R$ 96.23
Queremos descobrir quanto tempo levamos para executar essa busca. Para isso, voltaremos ao arquivo main.go
e criaremos uma variável start
no início da função main()
para armazenar o tempo atual com := time.Now()
. Descendo até o fim da função, após a execução da busca de preços, exibiremos o tempo total de execução com um fmt.Printf()
, recebendo a mensagem "\nTempo total: %s\n"
e a função time.Since(start)
:
package main
func main()
start := time.Now()
price1 := fetcher.FetchPriceFromSite1()
price2 := fetcher.FetchPriceFromSite2()
price3 := fetcher.FetchPriceFromSite3()
fmt.Printf("R$ %.2f \n", price1)
fmt.Printf("R$ %.2f \n", price2)
fmt.Printf("R$ %.2f \n", price3)
fmt.Printf("\nTempo total: %s", time.Since(start))
}
Executando novamente no terminal, veremos que o tempo total de execução foi de 6 segundos, somando os tempos de cada site.
R$ 0.87
R$ 44.75
R$ 72.06
Na sequência, descobriremos como a concorrência pode nos ajudar a executar esse programa de forma mais eficiente.
O objetivo agora é aplicar concorrência no projeto que estamos desenvolvendo. Primeiramente, precisamos entender como criar concorrência.
No Go, utilizamos a palavra-chave go
para isso. Sempre que utilizarmos go
seguido de uma função, estamos criando uma Goroutine (rotina do Go). Uma Goroutine é uma função ou método que será executado concorrentemente com outras Goroutines no mesmo espaço de endereçamento. Tudo isso é gerenciado pela runtime do Go.
Quando colocamos go
antes de uma função, essa função será executada de forma concorrente com outras funções. Isso significa que queremos que a runtime do Go gerencie e execute essas funções de maneira sofisticada.
Nosso objetivo é executar as três chamadas de busca de preço dos sites 1, 2 e 3 em Goroutines separadas. Para isso, vamos voltar ao arquivo main.go
.
No interior da função main()
, recortaremos a linha price3 = fetcher.FetchPricesFromSite3()
com o atalho "Ctrl + X" e o colocaremos entre as chaves de uma função anônima go func()
, que criaremos abaixo da linha start := time.Now()
.
Queremos que essa função seja executada assim que chegar nessa instrução. Portanto, adicionaremos um par de parênteses após o fechamento das chaves.
Faremos o mesmo para as funções 2 e 3, utilizando uma função anônima para cada.
main.go
:
// Código omitido
func main()
start := time.Now()
go func() {
price1 := fetcher.FetchPricesFromSite1()
}()
go func() {
price2 := fetcher.FetchPricesFromSite2()
}()
go func() {
price3 := fetcher.FetchPricesFromSite3()
}()
fmt.Printf("R$ %.2f \n", price1)
fmt.Printf("R$ %.2f \n", price2)
fmt.Printf("R$ %.2f \n", price3)
fmt.Printf("\nTempo total: %s", time.Since(start))
}
No entanto, enfrentamos um problema: os preços 1, 2 e 3 não estão visíveis. Precisamos saber de onde vêm esses preços, qual é o tipo desses dados e outras informações necessárias para manter o projeto funcionando.
Para resolver isso, criaremos variáveis acima das funções anônimas, chamadas price1
, price2
e price3
, todas do tipo float64
. Em vez de inicializá-las imediatamente dentro das funções com o sinal :=
, deixaremos apenas o sinal de igual, permitindo que o programa funcione corretamente.
// Código omitido
func main()
start := time.Now()
var price1, price2, price3 float64
go func() {
price1 = fetcher.FetchPricesFromSite1()
}()
go func() {
price2 = fetcher.FetchPricesFromSite2()
}()
go func() {
price3 = fetcher.FetchPricesFromSite3()
}()
fmt.Printf("R$ %.2f \n", price1)
fmt.Printf("R$ %.2f \n", price2)
fmt.Printf("R$ %.2f \n", price3)
fmt.Printf("\nTempo total: %s", time.Since(start))
}
Após criar as variáveis e as três Goroutines, esperamos que elas sejam executadas de forma concorrente. Ao executar o projeto no terminal com go run cmd/main.go
, perceberemos que ele exibe os preços zerados e o tempo de 46 microssegundos.
R$ 0,00
R$ 0,00
R$ 0,00
Tempo total: 46.167µs
Isso ocorreu porque as funções não tiveram tempo de concluir antes de exibir os preços.
Para resolver isso, precisamos criar uma variável que nos permita esperar a conclusão das Goroutines. Abaixo da declaração das variáveis de preço, criaremos uma variável chamada wg
, que significa Wait Group (grupo de espera).
O Go já possui isso nativamente no pacote sync
. Para utilizá-lo, adicionaremos um sync.WaitGroup
.
Após criar o Wait Group, precisamos informar que ele deve esperar a conclusão das três rotinas. Na linha seguinte, utilizaremos o método .Add()
a partir do wg
para adicionar um delta, que é um número inteiro representando a quantidade de Goroutines que ele deve gerenciar — neste caso, 3
.
Em seguida, solicitaremos ao Wait Group que espere a conclusão de cada rotina. Para isso, abaixo das três chamadas de funções anônimas e antes de exibir os nomes, adicionaremos um wg.Wait()
para solicitar essa espera.
// Código omitido
func main()
start := time.Now()
var price1, price2, price3 float64
var wg sync.WaitGroup
wg.Add(3)
go func() {
price1 = fetcher.FetchPricesFromSite1()
}()
go func() {
price2 = fetcher.FetchPricesFromSite2()
}()
go func() {
price3 = fetcher.FetchPricesFromSite3()
}()
wg.Wait()
fmt.Printf("R$ %.2f \n", price1)
fmt.Printf("R$ %.2f \n", price2)
fmt.Printf("R$ %.2f \n", price3)
fmt.Printf("\nTempo total: %s", time.Since(start))
}
Após as alterações, ao executar o projeto novamente no terminal, ainda teremos os preços zerados, pois enfrentamos um problema de deadlock.
fatal error: all goroutines are asleep - deadlock!
Isso ocorre quando as Goroutines estão esperando umas pelas outras, mas a ação esperada não acontece, fazendo com que o programa fique preso. Precisamos informar ao Wait Group quando cada função é concluída.
Para isso, adicionaremos uma instrução de defer
e utilizaremos um wg.Done()
dentro de cada função, na primeira linha entre suas chaves. O defer
garante que o Done()
será executado após todas as linhas da função serem executadas, indicando que a função foi concluída.
// Código omitido
func main()
start := time.Now()
var price1, price2, price3 float64
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
price1 = fetcher.FetchPricesFromSite1()
}()
go func() {
defer wg.Done()
price2 = fetcher.FetchPricesFromSite2()
}()
go func() {
defer wg.Done()
price3 = fetcher.FetchPricesFromSite3()
}()
wg.Wait()
fmt.Printf("R$ %.2f \n", price1)
fmt.Printf("R$ %.2f \n", price2)
fmt.Printf("R$ %.2f \n", price3)
fmt.Printf("\nTempo total: %s", time.Since(start))
}
Ao executar o projeto novamente com go run cmd/main.go
, ele funcionará corretamente, levando apenas três segundos para buscar os preços em diferentes sites.
R$ 14,85
R$ 8,83
R$ 61,58
Tempo total: 3.001274333s
Sabemos que a busca no primeiro site demora um segundo, a busca no segundo demora três segundos e a busca no terceiro demora dois segundos. Na sequência, entenderemos porque as três buscas foram realizadas tão rápido.
O curso Go: use concorrência para otimizar sua aplicação possui 97 minutos de vídeos, em um total de 41 atividades. Gostou? Conheça nossos outros cursos de GoLang em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.