Como injetar valores em variáveis com ldflags

Uma feature pouco conhecida para quem está iniciando na linguagem Go, é a capacidade de injetarmos valores em variáveis durante o processo de build. Embora possa parecer um tanto quanto estranho fazer isso, essa técnica nos possibilita adicionar informações como versão, data e commit do build, sem a necessidade de commitar essas informações. Embora possamos criar essas variáveis no mesmo arquivo onde estará a função main da aplicação, eu prefiro fazer algo que possa ser reutilizado.

Definindo as variáveis de build

Por isso, pensando em um repositório onde haverão várias aplicações, ou até mesmo em uma estrutura de monorepo, vamos criar um package dentro da pasta pkg chamado build. Dentro do package, vamos criar um arquivo chamado version.go com o seguinte conteúdo.

Leia mais »
green tree

Qual a diferença entre reflect.TypeOf e reflect.ValueOf

Se você é novo no mundo Go, muito provavelmente ainda não tenha trabalhado diretamente com o package reflect. Na verdade, é completamente normal pessoas que já trabalhem com a linguagem a algum tempo e ainda não tenham utilizado tal package.

Isso por que esse package tem uma utilidade muito específica. Não que todos os packages não tenham, ou pelo menos deveriam ter. Mas no caso do reflect, sua especificidade é tão grande, que mesmo em sistemas que o utilizam, ele provavelmente fica em alguma parte obscura, complexa e pouco mantida.

Tendo dito tudo isso e, ao mesmo tempo falado muito pouco, nesse post explicarei tudo o que você precisa saber sobre o package reflect e suas funções TypeOf e ValueOf.

Reflect

Antes de entender o que as funções TypeOf e ValueOf tem a oferecer, falemos um pouco sobre a finalidade do package reflect.

Leia mais »
blue threads

Quais as diferenças entre goroutines e threads

Um pensamento muito comum para quem está chegando na linguagem Go, ou só conhece a linguagem pelo o que “ouviu na rua”, é achar que as goroutines são threads.

Acontece que na prática, goroutines e threads, embora parecidas, são coisas bem diferentes. Vejamos.

Uma thread, nada mais é do que um espaço que o sistema operacional aloca na memória. Normalmente, uma thread ocupa um espaço de 2mb.

Quando uma goroutine é criada, ela normalmente ocupa 2kb de espaço em memória, o que dá aproximadamente 1% do tamanho de uma thread. Ou seja, para uma goroutine simples, ter uma thread inteira só para ela acaba sendo um grande desperdício de memória.

Embora modificar o tamanho das threads do sistema operacional pudesse resolver o problema de desperdício de memória em goroutines simples, isso acabaria gerando um grande problema para goroutines mais complexas, onde podemos ver seu consumo chegar na casa de gigas.

Leia mais »
photo of gray faucet

Como resolver memory leaks em maps

Uma das formas mais comuns de se fazer cache em aplicações Go é utilizando um map. Se você já fez isso, deve ter notado um aumento gradual no consumo de memória, e que normalmente após um restart da máquina ou pod volta ao “normal”.

Isso acontece devido a forma como o map funciona. Por isso, antes de ver o que podemos fazer para resolver esse tipo de problema, vamos entender melhor o map.

Para exemplificar o problema, vamos considerar uma variável do tipo map[int][128]byte, que será “carregada” com 1 milhão de elementos e que na sequência serão removidas.

package main

import (
	"fmt"
	"runtime"
)

func main() {
	n := 1_000_000
	m := make(map[int][128]byte)
	printAlloc()

	for i := 0; i < n; i++ {
		m[i] = [128]byte{}
	}
	printAlloc()

	for i := 0; i < n; i++ {
		delete(m, i)
	}

	runtime.GC()
	printAlloc()
	runtime.KeepAlive(m)
}

func printAlloc() {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	fmt.Printf("%d KB\\n", m.Alloc/1024)
}
Leia mais »
high angle view of people on bicycle

Como resolver race condition com sync/atomic

Algum tempo atrás, publicamos um post aqui no blog explicando como resolver race condition utilizando mutex e channels.

Embora o conteúdo daquele post continue sendo válido, para alguns casos mais simples de race condition, como por exemplo o do post, podemos utilizar o package sync/atomic para nos auxiliar.

Para dar o ponta pé inicial, vamos escrever um código que não irá funcionar corretamente por haver race condition.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var total int64

	var wg sync.WaitGroup

	for i := 0; i < 50; i++ {
		wg.Add(1)

		go func() {
			for c := 0; c < 1000; c++ {
				total += int64(c)
			}
			wg.Done()
		}()
	}

	wg.Wait()

	fmt.Println(total)
}
Leia mais »

Orientação a objetos em Go

Go não é uma linguagem de programação orientada a objetos. No entanto, algumas de suas features fazem com que seja possível trabalhar com algo muito parecido.

Nesse post vou falar sobre como podemos utilizar essas features para ter alguns comportamentos parecidos com orientação a objetos.

Classe

Go não implemente o conceito de classe como podemos encontrar em outras linguagens. Porém, para suprimir essa necessidade, podemos utilizar as structs ou estruturas.

type Foo struct {}

type bar struct {}

Esse tipo de dado composto nos permite criar campos, que podemos pensar como se fossem atributos. Também é possível adicionar métodos as structs.

Leia mais »

Múltiplos channels e a cláusula select

Dando continuidade ao nosso estudo de goroutines e channels, nesse post vamos falar sobre uma cláusula pouco utilizada.

Antes de começar, vou deixar os links para os outros posts caso você tenha perdido algum da série.

A cláusula select é utilizada para que uma função consiga trabalhar com múltiplos channels. Ela bloqueia a execução da função até que um dos channels esteja pronto para ser executado. Caso mais de um channel esteja pronto para ser executado, ela selecionará de forma aleatória qual executar.

Para tentar ficar um pouco mais claro, vamos escrever um pequeno programa para ilustrar o comportamento.

Leia mais »

Trabalhando com generics

Se você está no mundo Go a algum tempo, com certeza já ouviu falar desse carinha ai. Para aqueles que estão chegando agora, generics é uma das features mais pedidas/aguardadas desde que a linguagem foi liberada para o mundo.

Como já falamos em outros posts, Go é uma linguagem de tipagem forte, ou seja, sempre precisamos declarar o tipo da variável, parâmetros de uma função e seus retornos. Isso acaba fazendo com que, em algumas ocasiões, seja necessário duplicar nosso código só para que ele atenda dois tipos diferentes de dados.

Um exemplo básico desse problema seria uma função que soma uma lista de valores, onde sem generics, caso fossemos somar int64 e float64, seria necessário criar duas funções basicamente iguais, só mudando o tipo de dado do parâmetro e do retorno.

Leia mais »

Formatando strings para logs e mensagens

Se você já trabalha com Go a algum tempo, muito provavelmente você já conhece e utiliza o %s, %d e o \n. Pois bem, nesse post vamos abordar os principais os verbos disponíveis para te ajudar na hora de formatar uma string em Go.

Se você é novo em Go, saiba que os verbos que vamos abordar nesse post podem ser utilizados com as funções Printf, Sprintf e Errorf do package fmt, assim como as funções Fatalf, Panicf e Printf do package log.

Para começar, vamos a um exemplo bem simples utilizando somente os dois verbos que já comentamos. Digamos que nosso programa, sempre que alguém pede um novo café, exibe o nome da pessoa e a quantidade de café que ela consumiu no dia.

func main() {
    name := "Tiago"
    coffee := 5

    fmt.Printf("Olá %s, você já bebeu %d cafés hoje", name, coffee)
}

Ao ser executado, o %s será substituido pelo conteúdo da váriavel name, e o %d pela vafiável coffee.

Leia mais »

Aguardando execução de múltiplas goroutines

Pense em um cenário onde você abra centenas ou talvez milhares de goroutines, porém que essa quantidade não seja fixa.

Utilizar um channel para controlar a quantidade de goroutines que já finalizaram a execução pode ser muito trabalhoso e em alguns casos até impossível já que, para um channel simples, teriamos que esperar a execução de cada goroutine antes de iniciar a próxima.

Se resolvemos usar um channel com buffer, teremos que especificar sua capacidade durante sua criação, o que também pode nos levar a criar um buffer muito pequeno, onde não conseguimos iniciar todas as goroutines que queremos de uma só vez.

Se você quiser saber mais sobre goroutines e channels antes de continuar, vou deixar aqui a lista com os 3 últimos posts onde abordamos esses assuntos:

Bom, mas se gerir a execução das goroutines com channels pode ser trabalhoso ou em alguns casos até mesmo inviável, como podemos fazer?

Leia mais »