<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jean Grijp</title>
    <description>The latest articles on Forem by Jean Grijp (@jeangrijp).</description>
    <link>https://forem.com/jeangrijp</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F676863%2F586fef43-113e-4af6-95d8-d0208287bedc.jpeg</url>
      <title>Forem: Jean Grijp</title>
      <link>https://forem.com/jeangrijp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jeangrijp"/>
    <language>en</language>
    <item>
      <title>6 Go Libraries That Completely Transformed Software Development in 2025</title>
      <dc:creator>Jean Grijp</dc:creator>
      <pubDate>Tue, 30 Sep 2025 19:24:54 +0000</pubDate>
      <link>https://forem.com/jeangrijp/6-go-libraries-that-completely-transformed-software-development-in-2025-2d3b</link>
      <guid>https://forem.com/jeangrijp/6-go-libraries-that-completely-transformed-software-development-in-2025-2d3b</guid>
      <description></description>
    </item>
    <item>
      <title>Goroutines, threads e o caos coordenado do Go Scheduler.</title>
      <dc:creator>Jean Grijp</dc:creator>
      <pubDate>Thu, 24 Apr 2025 23:29:30 +0000</pubDate>
      <link>https://forem.com/jeangrijp/goroutines-threads-e-o-caos-coordenado-do-go-scheduler-2cg</link>
      <guid>https://forem.com/jeangrijp/goroutines-threads-e-o-caos-coordenado-do-go-scheduler-2cg</guid>
      <description>&lt;p&gt;Fala dev, estou compartilhando mais um doc que criei no Obsidian, espero que a leitura te ajude de alguma forma. Lembrando que estou aprendendo sobre o assunto, então se tu encontrar algo estranho ou errado, bora trocar ideia, comenta o post com educação e bora codar em Go.&lt;/p&gt;

&lt;h3&gt;
  
  
  O que é uma Thread?
&lt;/h3&gt;

&lt;p&gt;Uma &lt;em&gt;thread&lt;/em&gt; é uma unidade de execução dentro de um processo. É o que permite que um programa realize múltiplas tarefas aparentemente ao mesmo tempo. Em sistemas tradicionais, cada thread possui sua própria stack e é gerenciada pelo sistema operacional, com alto custo de criação e comutação. Em contrapartida, o Go abstrai esse conceito com as goroutines, que são unidades de execução mais leves que threads, gerenciadas pelo runtime do Go, muito mais leves e eficientes para tarefas concorrentes. Mesmo assim, entender o conceito de thread continua sendo útil, porque é a base sobre a qual a concorrência moderna foi construída.&lt;/p&gt;

&lt;p&gt;Cada thread, no contexto de programação, tem a sua &lt;strong&gt;própria &lt;a href="https://dev.to/jeangrijp/go-e-o-stack-invisivel-como-funcoes-e-goroutines-administram-memoria-nos-bastidores-2b7l"&gt;call stack&lt;/a&gt;&lt;/strong&gt;. Isso significa que quando você cria uma nova thread, o sistema operacional ou o runtime da linguagem aloca uma nova região de memória exclusiva pra ela, onde vai empilhar as chamadas de função feitas dentro dessa thread. Essa pilha é isolada das demais threads: se duas threads estiverem rodando ao mesmo tempo e ambas chamarem uma função chamada &lt;code&gt;processarDados()&lt;/code&gt;, cada uma delas vai ter a &lt;em&gt;sua própria versão empilhada&lt;/em&gt; de &lt;code&gt;processarDados()&lt;/code&gt; na sua respectiva stack. Elas não brigam pela mesma memória porque estão cada uma vivendo seu mundinho solitário de execução.&lt;/p&gt;

&lt;p&gt;A stack da thread é onde ficam armazenados os dados temporários da execução: parâmetros de função, variáveis locais e o endereço de retorno — ou seja, pra onde voltar quando a função acabar. Quando uma função é chamada, o frame é empilhado na stack da thread atual. Quando ela retorna, o frame é removido. É uma dança organizada de entrada e saída de contextos de execução, sempre seguindo o modelo LIFO: a última chamada é a primeira a sair.&lt;/p&gt;

&lt;p&gt;Agora, por que isso importa? Porque se você fizer besteira dentro de uma thread — tipo criar recursão infinita ou entupir a execução com chamadas profundas — &lt;em&gt;só a stack daquela thread vai explodir&lt;/em&gt;. As outras threads, felizes e inconscientes, continuam rodando como se nada tivesse acontecido.&lt;/p&gt;

&lt;p&gt;E em Go? A coisa muda um pouco, porque Go não usa threads diretamente. Ele usa goroutines, que são programaticamente muito mais leves, mas o princípio é parecido: &lt;strong&gt;cada goroutine tem sua própria stack separada&lt;/strong&gt;, e ela funciona da mesma forma que a stack de uma thread — só que com o charme extra de ser pequena, flexível e controlada pelo runtime em vez do sistema operacional.&lt;/p&gt;

&lt;h3&gt;
  
  
  Go Scheduler
&lt;/h3&gt;

&lt;p&gt;No Go, o agendador de goroutines — chamado carinhosamente de &lt;strong&gt;Go scheduler&lt;/strong&gt; — é o componente do runtime responsável por decidir &lt;strong&gt;qual goroutine roda, em qual thread, e quando&lt;/strong&gt;. E aqui vai o plot twist: o Go &lt;em&gt;não&lt;/em&gt; cria uma thread nova do sistema operacional pra cada goroutine. Se fizesse isso, seria como contratar um funcionário novo pra cada planilha que você abre — caro, lento, e só útil se você quiser falir seu computador.&lt;/p&gt;

&lt;p&gt;Em vez disso, o Go cria um pequeno grupo de &lt;strong&gt;threads reais&lt;/strong&gt; (do sistema operacional), chamadas de &lt;strong&gt;M&lt;/strong&gt; (&lt;em&gt;machine threads&lt;/em&gt;), e distribui as goroutines entre elas. Essas goroutines são gerenciadas inteiramente pelo runtime da linguagem, o que as torna semelhantes às chamadas &lt;em&gt;green threads&lt;/em&gt; ou &lt;em&gt;user-space threads&lt;/em&gt; — embora com diferenças importantes: no Go, múltiplas goroutines são mapeadas para múltiplas threads, não apenas uma.&lt;/p&gt;

&lt;p&gt;Cada &lt;code&gt;M&lt;/code&gt; precisa estar associada a um &lt;code&gt;P&lt;/code&gt;, de &lt;em&gt;Processor&lt;/em&gt;, que é uma unidade lógica do Go usada para coordenar a execução de código Go. O modelo de execução é conhecido como &lt;strong&gt;G:M:P&lt;/strong&gt;, onde:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;G&lt;/code&gt; é a goroutine (o que será executado),&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;M&lt;/code&gt; é a thread do sistema operacional (o que executa),&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;P&lt;/code&gt; é o processador lógico (o que permite que a execução ocorra).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quer um exemplo? Suponha que seu programa tenha &lt;code&gt;GOMAXPROCS=4&lt;/code&gt;. Isso quer dizer que o runtime do Go vai criar 4 &lt;strong&gt;P&lt;/strong&gt; — ou seja, vai usar até 4 núcleos da CPU simultaneamente. Esses P’s são os “slots de execução”: cada &lt;code&gt;P&lt;/code&gt; permite que uma &lt;code&gt;M&lt;/code&gt; execute goroutines, empilhando e desempilhando chamadas dentro da stack exclusiva da goroutine. O runtime coordena essa execução, e as threads (&lt;code&gt;M&lt;/code&gt;) vão pegando e executando as goroutines disponíveis.&lt;/p&gt;

&lt;p&gt;O agendador decide &lt;em&gt;quando&lt;/em&gt; uma goroutine deve parar de rodar e outra deve assumir o controle. Isso acontece, por exemplo, quando uma goroutine realiza uma operação bloqueante (como I/O), ou atinge um &lt;strong&gt;ponto seguro de preempção&lt;/strong&gt; — como loops longos ou chamadas a funções específicas. Nesse momento, o scheduler salva o estado da goroutine (inclusive a stack!) e coloca outra no lugar, como quem troca um funcionário sonolento por outro animado na linha de produção.&lt;/p&gt;

&lt;p&gt;O mais divertido (ou caótico, se você for o tipo de dev que gosta de controle absoluto) é que tudo isso é &lt;strong&gt;transparente&lt;/strong&gt;. Você escreve um código com &lt;code&gt;go minhaFuncao()&lt;/code&gt;, e de repente tem mil goroutines rodando “em paralelo” — mesmo que elas estejam dividindo umas poucas threads reais por trás das cortinas. Cada goroutine mantém sua própria stack, &lt;strong&gt;pequena e dinâmica&lt;/strong&gt;, e o runtime gerencia o show: cresce stacks sob demanda, troca contextos, e faz você parecer um gênio da concorrência mesmo sem saber quantos &lt;code&gt;M&lt;/code&gt;, &lt;code&gt;P&lt;/code&gt; e &lt;code&gt;G&lt;/code&gt; estão trabalhando nos bastidores.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mas o que é esse tal de &lt;code&gt;P&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;No modelo de agendamento do Go, o &lt;code&gt;P&lt;/code&gt; (de &lt;em&gt;Processor&lt;/em&gt;) representa uma &lt;strong&gt;unidade lógica de execução&lt;/strong&gt; controlada pelo runtime. Ele não é um thread nem uma goroutine, mas sim o &lt;strong&gt;intermediário essencial&lt;/strong&gt; que gerencia o agendamento de goroutines e fornece contexto para que o código Go seja executado por uma thread (&lt;code&gt;M&lt;/code&gt;). Cada &lt;code&gt;P&lt;/code&gt; possui uma &lt;strong&gt;fila local de goroutines prontas para execução&lt;/strong&gt;, conhecida como &lt;strong&gt;run queue&lt;/strong&gt;. O runtime sempre tenta consumir essa fila local primeiro, o que melhora o desempenho ao evitar sincronização com outras estruturas globais e aproveita melhor o cache da CPU.&lt;/p&gt;

&lt;p&gt;Quando uma thread (&lt;code&gt;M&lt;/code&gt;) está associada a um &lt;code&gt;P&lt;/code&gt;, ela pode executar goroutines da run queue daquele &lt;code&gt;P&lt;/code&gt;. Caso essa fila esteja vazia, o runtime tenta buscar novas goroutines da &lt;strong&gt;fila global&lt;/strong&gt;, ou até mesmo &lt;strong&gt;roubar goroutines de outro &lt;code&gt;P&lt;/code&gt;&lt;/strong&gt;, numa estratégia conhecida como &lt;strong&gt;work stealing&lt;/strong&gt;. Esse mecanismo evita desequilíbrios entre processadores lógicos e mantém a carga distribuída mesmo em cenários de concorrência intensa.&lt;/p&gt;

&lt;p&gt;O número de &lt;code&gt;P&lt;/code&gt;s ativos é determinado por &lt;code&gt;GOMAXPROCS&lt;/code&gt;, o que significa que, mesmo que existam milhares de goroutines, apenas um número limitado (igual ao de &lt;code&gt;P&lt;/code&gt;s) pode estar executando código Go simultaneamente. As demais ficam na fila aguardando sua vez.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chamadas bloqueantes (syscalls) e uso inteligente de threads (&lt;code&gt;M&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Em Go, uma goroutine pode, eventualmente, fazer uma chamada ao sistema operacional que bloqueia a thread. Isso acontece em situações comuns, como chamadas de rede, leitura de arquivos ou qualquer operação de I/O via syscall. Quando isso ocorre, a thread (&lt;code&gt;M&lt;/code&gt;) que estava executando a goroutine &lt;strong&gt;fica bloqueada&lt;/strong&gt; até que a operação termine.&lt;/p&gt;

&lt;p&gt;Agora vem a parte crítica: se o runtime simplesmente deixasse o &lt;code&gt;P&lt;/code&gt; associado a essa &lt;code&gt;M&lt;/code&gt; bloqueada, o programa perderia a capacidade de executar novas goroutines naquele slot de execução — o que resultaria em desperdício de recursos e perda de desempenho. Para evitar isso, o Go runtime &lt;strong&gt;desassocia o &lt;code&gt;P&lt;/code&gt; da thread bloqueada (&lt;code&gt;M&lt;/code&gt;)&lt;/strong&gt; e o entrega a outra thread que esteja disponível. Dessa forma, o &lt;code&gt;P&lt;/code&gt; continua executando outras goroutines prontas na fila, e o programa segue rodando com eficiência, mesmo durante operações de I/O demoradas. Se não houver nenhuma &lt;code&gt;M&lt;/code&gt; disponível no momento, o runtime pode &lt;strong&gt;criar uma nova thread real&lt;/strong&gt; do sistema operacional. Isso é feito de maneira controlada, com limites internos, para evitar que o programa crie um número excessivo de threads e sobrecarregue o sistema.&lt;/p&gt;

&lt;p&gt;Essa estratégia de &lt;strong&gt;desacoplamento entre o agendamento das goroutines e o bloqueio do sistema operacional&lt;/strong&gt; é um dos principais motivos pelos quais o Go consegue lidar tão bem com aplicações altamente concorrentes — como servidores HTTP, workers de fila, ou qualquer sistema com múltiplas operações simultâneas que dependem de I/O. Além disso, é importante destacar que o Go diferencia bem &lt;strong&gt;código Go puro&lt;/strong&gt; (que pode ser interrompido, agendado, migrado entre threads) de &lt;strong&gt;código que envolve chamadas nativas ou externas&lt;/strong&gt;, onde o controle de execução passa, mesmo que temporariamente, para o SO.&lt;/p&gt;

&lt;h3&gt;
  
  
  Um pouco mais sobre stacks dinâmicas das goroutines
&lt;/h3&gt;

&lt;p&gt;Uma das características mais importantes das goroutines é o uso de &lt;strong&gt;stacks dinâmicas&lt;/strong&gt;, que são controladas inteiramente pelo runtime do Go. Diferente das threads tradicionais do sistema operacional que geralmente alocam uma stack fixa de 1 MB ou mais, cada goroutine começa com uma stack extremamente pequena, geralmente &lt;strong&gt;apenas 2 KB&lt;/strong&gt;. Essa escolha reduz drasticamente o custo de criação de novas goroutines e é um dos principais motivos pelos quais é possível criar &lt;strong&gt;milhares ou até milhões de goroutines&lt;/strong&gt; em uma única aplicação Go sem comprometer a memória do sistema. A medida que a execução da goroutine avança, se o espaço da stack se tornar insuficiente (por exemplo, devido a chamadas de função aninhadas ou uso intensivo de variáveis locais), o runtime detecta essa situação e &lt;strong&gt;realoca a stack em uma região maior de memória&lt;/strong&gt;, copiando seu conteúdo atual e ajustando os ponteiros automaticamente. Esse processo é &lt;strong&gt;transparente para o desenvolvedor&lt;/strong&gt; e não exige nenhuma intervenção manual. Além disso, o runtime é capaz de &lt;strong&gt;reduzir o tamanho da stack&lt;/strong&gt; de uma goroutine quando percebe que ela não precisa mais de tanta memória. Essa &lt;strong&gt;flexibilidade bidirecional&lt;/strong&gt; permite uma gestão de memória muito mais eficiente do que stacks fixas, e ajuda a manter a escalabilidade e responsividade da aplicação.&lt;/p&gt;

&lt;p&gt;Vale lembrar que essa operação de crescimento (ou encolhimento) da stack pode ter custo de desempenho pontual, especialmente se ocorrer com frequência. Por isso, em cenários críticos de performance, é possível otimizar o uso da stack escrevendo funções com profundidade de chamada controlada e evitando estruturas muito grandes em variáveis locais.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Como o Go lida com preempção&lt;/strong&gt;, ou seja, como ele lida com aquelas goroutines folgadas que decidem nunca mais sair da CPU?
&lt;/h3&gt;

&lt;p&gt;O agendador do Go precisa garantir que nenhuma goroutine fique se achando a dona da CPU. Pra isso, existe o conceito de &lt;strong&gt;preempção&lt;/strong&gt;, que basicamente é o runtime dizendo “chega, tua vez acabou, deixa o coleguinha brincar”. Só que o Go não faz isso de forma brutal, como alguns sistemas operacionais que simplesmente pausam a thread no meio de qualquer instrução. Em vez disso, o Go usa o modelo de &lt;strong&gt;preempção cooperativa&lt;/strong&gt;, o que significa que ele só pode interromper a execução de uma goroutine &lt;strong&gt;em pontos seguros&lt;/strong&gt; do código, lugares estrategicamente definidos onde o runtime sabe que pode pausar sem quebrar tudo.&lt;/p&gt;

&lt;p&gt;Esses pontos seguros (ou &lt;em&gt;safe points&lt;/em&gt;, se você quiser parecer mais chique) são colocados pelo compilador em lugares como o início de loops, chamadas de função ou outras instruções que o runtime monitora. A ideia é evitar que uma goroutine fique rodando pra sempre sem dar chance pras outras, tipo aquele colega de reunião que não larga o microfone.&lt;/p&gt;

&lt;p&gt;Antes do Go 1.14, isso era um problema sério. Se uma goroutine entrasse num loop longo sem fazer nenhuma chamada externa, ela podia simplesmente &lt;strong&gt;monopolizar um núcleo da CPU&lt;/strong&gt; e impedir que outras goroutines rodassem. Era como ter um funcionário que esqueceu de sair pra almoço e não deixa ninguém mais usar a sala de reunião. A partir do Go 1.14, o runtime ganhou um superpoder: o compilador passou a inserir instruções especiais nos loops e outros trechos críticos, permitindo que o agendador dê aquele &lt;strong&gt;“tapinha no ombro” da goroutine&lt;/strong&gt; e diga: "tempo esgotado, agora deixa o próximo". Quando isso acontece, o estado da execução é salvo e outra goroutine assume a execução no mesmo &lt;code&gt;P&lt;/code&gt;. Esse esquema garante que nenhuma goroutine abuse do tempo de CPU e ajuda o Go a manter o show rodando suave — sem threads explodindo, sem pausas indesejadas, e sem você precisar escrever uma linha de código pra gerenciar isso.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitações e armadilhas do Go Scheduler (sim, ele também tem seus dias ruins)
&lt;/h3&gt;

&lt;p&gt;O Go scheduler é ótimo — ele faz muito com pouco: organiza milhares de goroutines com algumas threads reais, empilha, desempilha, redistribui... tudo isso quase sem você notar. Mas ele &lt;strong&gt;não é mágico nem infalível&lt;/strong&gt;. Em algumas situações específicas, ele pode se tornar um &lt;strong&gt;gargalo&lt;/strong&gt;, especialmente quando a arquitetura da aplicação força os limites do modelo &lt;code&gt;G:M:P&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Um dos principais pontos de atenção está no valor de &lt;code&gt;GOMAXPROCS&lt;/code&gt;. Como esse valor define quantos &lt;code&gt;P&lt;/code&gt;s existem, ele &lt;strong&gt;limita o número de goroutines que podem executar código Go ao mesmo tempo&lt;/strong&gt;. Se &lt;code&gt;GOMAXPROCS=2&lt;/code&gt;, por exemplo, mesmo que você tenha 10 mil goroutines prontas pra rodar, só duas vão de fato executar simultaneamente. As outras ficam esperando na fila. Isso é ótimo pra controle de paralelismo, mas pode gerar filas grandes e latência inesperada se o número de goroutines prontas crescer rápido demais.&lt;/p&gt;

&lt;p&gt;Outro fator crítico: o scheduler não tem conhecimento semântico do que suas goroutines estão fazendo. Ele não sabe se a goroutine está processando uma requisição urgente ou só atualizando um cache irrelevante. O escalonamento é feito com base em prontidão, não em prioridade. Isso significa que goroutines com workloads diferentes podem &lt;strong&gt;competir igualmente por tempo de execução&lt;/strong&gt;, o que nem sempre é ideal. Em alguns casos, goroutines importantes podem ser preemptadas por outras menos críticas, impactando a qualidade do serviço.&lt;/p&gt;

&lt;p&gt;O scheduler também pode sofrer se você tiver &lt;strong&gt;muitas goroutines prontas ao mesmo tempo&lt;/strong&gt;, mas com uma lógica que exige muita alternância entre elas (ex: milhares de canais se comunicando em alta frequência). Isso pode causar overhead de troca de contexto e pressionar a fila global de goroutines — que, diferente das filas locais dos &lt;code&gt;P&lt;/code&gt;s, exige lock e pode se tornar um &lt;strong&gt;ponto de contenção&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Além disso, é comum ver devs criando goroutines demais sem pensar no lifecycle delas. Se você esquece de controlar o tempo de vida de uma goroutine (como não cancelá-la via context, por exemplo), elas continuam vivas, mesmo que não estejam fazendo nada útil. Isso gera &lt;strong&gt;acúmulo silencioso&lt;/strong&gt;: pilhas crescendo, memória sendo usada à toa, e mais carga sobre o agendador.&lt;/p&gt;

&lt;p&gt;Por fim, como o Go não tem preempção baseada em tempo real (como certos kernels de SO), uma goroutine que faz &lt;strong&gt;uso intensivo de CPU sem pontos de preempção&lt;/strong&gt; ainda pode gerar starvation (as outras goroutines ficam sem tempo de execução). Isso é raro com as melhorias do Go 1.14+, mas ainda possível se você escrever código que "engole" o processador por muito tempo sem pausas.&lt;/p&gt;

&lt;h3&gt;
  
  
  chegou até aqui?
&lt;/h3&gt;

&lt;p&gt;Entender como o Go lida com agendamento, preempção, chamadas bloqueantes e stacks minúsculas não só te ajuda a escrever código mais eficiente, como também evita que você saia criando goroutine igual panfleto em semáforo e depois não saiba de onde vem o vazamento de memória. Se esse artigo te ajudou, manda pra aquele(a) amigo(a) que acha que &lt;code&gt;go func() {}&lt;/code&gt; é solução mágica pra tudo. E se você encontrou algum erro, manda também, estou aprendendo sobre o tema, e além disso porque o scheduler pode escalar milhares de goroutines, mas eu ainda não escalo múltiplas versões de mim mesmo.&lt;/p&gt;

&lt;p&gt;Valeu demais por acompanhar. Até a próxima execução paralela. #Beba_agua.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Go e o Stack Invisível: Como Funções e Goroutines Administram Memória nos Bastidores.</title>
      <dc:creator>Jean Grijp</dc:creator>
      <pubDate>Tue, 22 Apr 2025 12:54:21 +0000</pubDate>
      <link>https://forem.com/jeangrijp/go-e-o-stack-invisivel-como-funcoes-e-goroutines-administram-memoria-nos-bastidores-2b7l</link>
      <guid>https://forem.com/jeangrijp/go-e-o-stack-invisivel-como-funcoes-e-goroutines-administram-memoria-nos-bastidores-2b7l</guid>
      <description>&lt;p&gt;Comecei a estudar sobre a Call Stack do Go e percebi que seria legal compartilhar o resultado, então eu espero que esse post ajude a quem estiver aprendendo Go também. Ao longo da explicação dos conceitos, pode ser que eu repita algo que já expliquei, mas a intenção é reforçar o conceito para eliminar possíveis dúvidas, mas claro, sem deixar o texto extremamente repetitivo. beba água.&lt;/p&gt;

&lt;p&gt;A &lt;em&gt;stack&lt;/em&gt;, ou pilha de execução, é uma estrutura de dados usada internamente pelas linguagens de programação para controlar a ordem de execução das funções. Ela segue uma lógica chamada &lt;strong&gt;LIFO&lt;/strong&gt;, que significa "Last In, First Out" — ou seja, a última função que entrou na pilha é a primeira que vai sair. É como empilhar livros: o último que você colocou em cima é o primeiro que você vai tirar. Essa pilha não é um conceito visual bonitinho só pra programador iniciante se sentir incluído; ela é real, ela mora na memória do processo e serve pra guardar o que está acontecendo a cada momento da execução.&lt;/p&gt;

&lt;p&gt;Quando uma função é chamada, o programa empilha um &lt;strong&gt;frame&lt;/strong&gt;, que é como um pacote com informações: quais são os argumentos da função, quais variáveis ela declarou internamente, e — talvez o mais importante — qual o endereço de memória que ele precisa voltar depois que essa função terminar. Assim que a função conclui seu trabalho, esse frame é retirado da Stack, e o controle volta pra função anterior, que estava de braços cruzados esperando isso acontecer.&lt;/p&gt;

&lt;p&gt;Esse processo de empilhar e desempilhar acontece de forma automática, você não precisa fazer nada. A não ser que você seja um desenvolvedor em Go querendo entender por que o seu programa explodiu com um &lt;code&gt;stack overflow&lt;/code&gt;, o que geralmente significa que uma função chamou a si mesma tantas vezes que a pilha não teve mais espaço pra guardar os frames.&lt;/p&gt;

&lt;p&gt;No Go, cada função que você chama também entra na stack, como em qualquer linguagem procedural que não acordou ontem. A diferença está na forma como o Go gerencia essa stack por trás das cortinas. Em vez de criar uma stack grandona e fixa por thread, como acontece em linguagens como C ou Java, o Go tem um modelo mais... econômico. Cada goroutines (que é uma unidade de execução leve, menor que uma thread, e gerenciada pelo runtime do Go) começa com uma stack minúscula, normalmente em torno de 2 KB, isso é ridiculamente pequeno.&lt;/p&gt;

&lt;p&gt;Essa stack pequenininha é &lt;strong&gt;dinâmica&lt;/strong&gt;. O que significa isso? Significa que, à medida que a função chama outra função, e assim por diante, o Go percebe quando está ficando apertado e realoca a stack para um espaço maior. Sim, ele literalmente copia a pilha inteira para outro lugar na memória. Parece ineficiente, mas na prática é bem rápido e muito mais econômico do que reservar 1 MB por goroutine só porque você tem sonhos grandes demais. É esse esquema que permite ao Go criar centenas de milhares de goroutines sem mandar sua máquina pro hospital.&lt;/p&gt;

&lt;p&gt;Mas nem tudo são flores, como o stack é móvel, o Go precisa tomar muito cuidado com ponteiros. Diferente de C, onde um ponteiro pode apontar pro nada e te causar pesadelos, no Go o runtime cuida de atualizar tudo quando o stack é movido.&lt;/p&gt;

&lt;p&gt;Além disso, quando ocorre um erro, como um &lt;code&gt;panic&lt;/code&gt;, o Go consegue imprimir um &lt;strong&gt;stack trace&lt;/strong&gt; que mostra exatamente a cadeia de chamadas que levou ao desastre. O stack trace é construído a partir das informações mantidas pelo runtime, que incluem os frames da stack e os metadados de chamadas. Não é super detalhado como um debugger de verdade, mas serve pra você olhar com vergonha e dizer: “é, fui eu mesmo que escrevi essa aberração.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Como o Go sabe que precisa crescer o stack?
&lt;/h3&gt;

&lt;p&gt;Isso é onde o Go fica metido a esperto. Toda vez que uma função é chamada, o runtime precisa garantir que haja espaço suficiente no stack da goroutine para que a função execute. Isso inclui espaço para variáveis locais, argumentos e o que mais for necessário dentro daquele frame. Só que, lembra? O stack começa ridiculamente pequeno. Pra resolver isso, o compilador do Go insere automaticamente um código de verificação no início de cada função. Isso é conhecido como o famoso &lt;strong&gt;stack split check&lt;/strong&gt;. Traduzindo do Goês para o Idioma dos Mortais: é uma checagem que pergunta "tenho espaço suficiente pra rodar essa função aqui ou não?"&lt;/p&gt;

&lt;p&gt;Se a resposta for “sim”, maravilha. A função executa normalmente e ninguém sofre. Se a resposta for “não”, o Go chama uma função especial, &lt;code&gt;newstack()&lt;/code&gt;, que realiza o crescimento da pilha. É uma função interna, mas não é chamada como uma função “normal”. O código gerado pelo compilador faz uma chamada especial a essa função em assembly.&lt;/p&gt;

&lt;p&gt;Agora vem a parte legal (ou trágica, depende de quanto você gosta de ver seu programa fazendo cópia de memória no meio da execução): essa função &lt;code&gt;newstack()&lt;/code&gt; cria uma nova área de memória maior, &lt;strong&gt;copia todo o conteúdo da stack atual pra lá&lt;/strong&gt;, ajusta os ponteiros, e depois continua a execução como se nada tivesse acontecido. É como trocar o chão enquanto você ainda está andando — e torcer pra ninguém perceber.&lt;/p&gt;

&lt;p&gt;Isso funciona porque o Go controla todo o ambiente da goroutine. Nenhum ponteiro externo aponta diretamente pro stack (pelo menos, não deveria — o compilador e o &lt;strong&gt;garbage collector&lt;/strong&gt; trabalham pra evitar isso), então é seguro mover tudo sem causar um incêndio na memória. Por isso, o Go consegue crescer stacks conforme a necessidade sem causar overhead constante. Você paga o preço da realocação só quando realmente precisa. Isso também explica por que goroutines são tão leves: elas só consomem o que for necessário, e vão crescendo conforme o uso real, em vez de começar como um trambolho de 1 MB cada uma.&lt;/p&gt;

&lt;h3&gt;
  
  
  Heap x Stack
&lt;/h3&gt;

&lt;p&gt;O gerenciamento de memória é dividido entre duas regiões principais: a &lt;strong&gt;stack&lt;/strong&gt; e o &lt;strong&gt;heap&lt;/strong&gt;. A stack é usada para armazenar variáveis locais de curta duração, parâmetros de função e tudo que pode ser descartado rapidamente assim que a função termina. Cada vez que uma função é chamada, o Go aloca um pequeno espaço na stack — chamado de &lt;em&gt;stack frame&lt;/em&gt; — onde ele guarda todas as informações relacionadas àquela execução. Esse espaço inclui os argumentos passados, variáveis locais e um ponteiro que indica onde a execução deve continuar quando a função retornar. Quando a função termina, esse frame é simplesmente removido da stack, liberando o espaço de forma rápida e eficiente.&lt;/p&gt;

&lt;p&gt;A stack no Go é única para cada &lt;strong&gt;goroutine&lt;/strong&gt;. Isso significa que toda goroutine que você cria tem sua própria pilha, independente das outras. Essas stacks são minúsculas por padrão (começam com apenas 2KB), mas têm a habilidade mágica de crescer dinamicamente se for necessário, através de um processo chamado &lt;strong&gt;stack splitting&lt;/strong&gt;. Isso permite que milhares ou até milhões de goroutines coexistam sem fritar sua máquina, o que é uma façanha que linguagens mais ortodoxas como Java só conseguem com oração.&lt;/p&gt;

&lt;p&gt;Já o heap é onde moram as variáveis que o compilador acha que vão viver por mais tempo, ou que precisam ser acessadas fora do escopo da função onde nasceram. O Go decide o destino de cada variável usando uma técnica chamada &lt;strong&gt;escape analysis&lt;/strong&gt;, que basicamente responde à pergunta: “essa variável vai vazar do contexto onde foi criada?” Se sim, ela vai direto pro heap. Isso acontece, por exemplo, quando você retorna o ponteiro de uma variável local, ou quando uma função armazena referências em uma estrutura que vai sobreviver à execução atual.&lt;/p&gt;

&lt;p&gt;As funções em si — ou seja, o código delas — não vão pra heap nem pra stack. Elas são armazenadas numa região separada da memória chamada &lt;strong&gt;text segment&lt;/strong&gt;, que é onde vive o binário compilado. Quando você chama uma função, o Go basicamente empilha um novo frame na stack da goroutine ativa e pula praquele endereço no text segment. Quando ela termina, ele volta pro ponto anterior e a vida segue.&lt;/p&gt;

&lt;p&gt;Esse modelo é extremamente eficiente. Usar a stack como área principal de alocação temporária reduz a carga do &lt;strong&gt;garbage collector&lt;/strong&gt;, que só precisa se preocupar com as variáveis no heap. Mas como nem tudo é perfeito, você ainda precisa estar ciente de quando suas variáveis vão parar lá, pra evitar alocações desnecessárias e gargalos invisíveis de desempenho. E sim, isso significa que &lt;em&gt;às vezes&lt;/em&gt; vale a pena olhar os relatórios de escape do compilador — por mais deprimentes que eles sejam.&lt;/p&gt;

&lt;h4&gt;
  
  
  As funções em Go
&lt;/h4&gt;

&lt;p&gt;Já as funções, diferentemente das variáveis, têm um destino completamente diferente. O código da função em si é armazenado em uma região especial da memória chamada text segment, também conhecida como code segment. Essa parte da memória é onde vive o código executável do programa. Quando você chama uma função, a CPU salta para o endereço correspondente no text segment, e as variáveis que a função usa são empilhadas na stack ou alocadas no heap, dependendo do que o compilador decidir.&lt;/p&gt;

&lt;p&gt;Essa arquitetura torna o Go extremamente eficiente para lidar com concorrência e múltiplas goroutines. Cada goroutine tem sua própria stack que começa pequena e pode crescer de forma dinâmica, permitindo que você tenha milhares de tarefas leves rodando ao mesmo tempo. Ao mesmo tempo, a separação clara entre stack, heap e text segment ajuda o compilador a otimizar a execução e o uso de memória de forma bastante eficaz.&lt;/p&gt;

&lt;h4&gt;
  
  
  Alocação na Stack
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"fmt"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;
    &lt;span class="n"&gt;printValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;printValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nesse exemplo, a variável &lt;code&gt;x&lt;/code&gt; é um valor simples. Ele é passado por &lt;strong&gt;valor&lt;/strong&gt; para a função &lt;code&gt;printValue&lt;/code&gt;, e não é retornado, nem usado fora do &lt;code&gt;main&lt;/code&gt;. Como nada escapa, o compilador é feliz e aloca tudo isso na &lt;strong&gt;stack&lt;/strong&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  Alocação na Heap
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"fmt"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;createPointer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;createPointer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora temos o mesmo valor, &lt;code&gt;42&lt;/code&gt;, e o mesmo tipo de variável. Mas olha o crime aqui: estamos &lt;strong&gt;retornando um ponteiro para uma variável local&lt;/strong&gt;. A stack de &lt;code&gt;createPointer&lt;/code&gt; vai sumir assim que a função retornar, então se &lt;code&gt;x&lt;/code&gt; estivesse ali, estaríamos acessando memória morta. O compilador detecta isso e joga &lt;code&gt;x&lt;/code&gt; direto no &lt;strong&gt;heap&lt;/strong&gt;, garantindo que ele sobreviva após o retorno.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consideração final
&lt;/h2&gt;

&lt;p&gt;Se você chegou até aqui, parabéns. Você agora sabe mais sobre a &lt;em&gt;call stack&lt;/em&gt; do Go do que 90% das pessoas que colocam "Golang ninja" no LinkedIn. E se em algum momento achou que a stack era só um lugarzinho onde variáveis iam tirar uma soneca, sinto informar que ela é mais parecida com uma república de estudantes: todo mundo entra, ninguém limpa nada, e eventualmente alguém causa um &lt;em&gt;panic&lt;/em&gt; e tudo desmorona.&lt;/p&gt;

&lt;p&gt;Mas agora você sabe o que está acontecendo por trás das cortinas — ou melhor, das &lt;em&gt;goroutines&lt;/em&gt; — e pode finalmente olhar para um &lt;code&gt;stack trace&lt;/code&gt; sem chorar (muito). E lembre-se: se algo deu errado, provavelmente foi porque você retornou o ponteiro errado…&lt;/p&gt;

&lt;h1&gt;
  
  
  beba_água.
&lt;/h1&gt;

</description>
      <category>go</category>
      <category>goroutines</category>
      <category>stack</category>
      <category>learning</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Jean Grijp</dc:creator>
      <pubDate>Mon, 06 Jan 2025 02:58:49 +0000</pubDate>
      <link>https://forem.com/jeangrijp/-3na6</link>
      <guid>https://forem.com/jeangrijp/-3na6</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/wesley_bispo_1703" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2647468%2F43b3dd9e-a3a2-4cb9-aef0-0a314443a67e.jpg" alt="wesley_bispo_1703"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/wesley_bispo_1703/como-sao-feitas-as-alocacoes-2dl4" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Entendendo Heap e Stack no Gerenciamento de Memória&lt;/h2&gt;
      &lt;h3&gt;Wesley Bispo ・ Jan 3&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#softwareengineering&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#go&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#learning&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#performance&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
    </item>
  </channel>
</rss>
