{"id":13491466,"url":"https://github.com/cassiobotaro/concorrencia-go","last_synced_at":"2025-10-12T03:30:28.730Z","repository":{"id":147441140,"uuid":"477179857","full_name":"cassiobotaro/concorrencia-go","owner":"cassiobotaro","description":"⛓️ Estudos sobre concorrência em Go","archived":false,"fork":false,"pushed_at":"2024-07-28T22:38:17.000Z","size":55,"stargazers_count":104,"open_issues_count":0,"forks_count":8,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-05T04:08:21.816Z","etag":null,"topics":["concorrencia","go","padroes-de-concorrencia"],"latest_commit_sha":null,"homepage":"https://abre.ai/pc-gobr-2024","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cassiobotaro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-02T21:54:24.000Z","updated_at":"2025-03-26T00:24:32.000Z","dependencies_parsed_at":null,"dependency_job_id":"f62c4706-95f3-48a5-8ad4-a20e19d93f15","html_url":"https://github.com/cassiobotaro/concorrencia-go","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cassiobotaro/concorrencia-go","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cassiobotaro%2Fconcorrencia-go","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cassiobotaro%2Fconcorrencia-go/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cassiobotaro%2Fconcorrencia-go/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cassiobotaro%2Fconcorrencia-go/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cassiobotaro","download_url":"https://codeload.github.com/cassiobotaro/concorrencia-go/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cassiobotaro%2Fconcorrencia-go/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279010148,"owners_count":26084692,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-12T02:00:06.719Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["concorrencia","go","padroes-de-concorrencia"],"created_at":"2024-07-31T19:00:57.292Z","updated_at":"2025-10-12T03:30:28.717Z","avatar_url":"https://github.com/cassiobotaro.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# Concorrência em Go\n\nGo é fundamentada no modelo CSP (Communicating sequential processes) proposto por Tony Hoare. Neste modelo, os dados são compartilhados enviando mensagens através de canais.\n\nAs explicações e exemplos são altamente inspirados na [apresentação](https://github.com/andrebq/andrebq.github.io) do @andrebq.\n\nUma outra influência é o [artigo](https://go.dev/blog/pipelines) sobre _pipelines_ e cancelamento em Go.\n\nAqui serão apresentados alguns padrões de concorrência, porém sugiro também a leitura sobre [context](https://gobyexample.com/context), [select](https://gobyexample.com/select), [canais com buffer](https://gobyexample.com/channel-buffering) e outros mecanismos de controle de concorrência.\n\n## 🔗 Canais\n\nCanais (channels) são uma estrutura primitiva na linguagem, e você pode utilizá-los para envio e recebimento de valores entre rotinas (_goroutines_). Os valores podem ser de qualquer tipo, inclusive do tipo canal.\n\nUm canal é um ponto de sincronização entre _goroutines_. Uma _goroutine_ vai ficar bloqueada escrevendo em um canal até que aquele canal seja lido.\n\nLer de um canal é semelhante, uma _goroutine_ vai ficar bloqueada lendo até que um valor seja enviado para o canal ou o canal seja fechado (quando isso ocorre, o valor zero do tipo é retornado).\n\nUm canal pode ser fechado. Isso é útil para indicar que nenhum outro valor será escrito no canal.\n\nLer um canal fechado retorna um valor zero do tipo do canal.\n\nEscrever em um canal fechado retorna um erro (_panic_).\n\n## 🗺️ Olá Mundo\n\nO [primeiro exemplo](./ola_mundo/ola_mundo.go) mostra como criar um canal, que será utilizado como ponte entre a aplicação principal e uma _goroutine_.\n\nO programa principal fica bloqueado até que a mensagem \"Olá mundo\" seja enviada para o canal.\n\nQuando isto ocorre, a _goroutine_ é desbloqueada e a mensagem é exibida.\n\nQuando o programa principal termina, a _goroutine_ é também terminada.\n\n```go\npackage main\n\nimport \"fmt\"\n\nfunc main() {\n\tcanal := make(chan string)\n\tgo func() {\n\t\tcanal \u003c- \"Olá, mundo!\"\n\t}()\n\n\tfmt.Println(\u003c-canal)\n}\n```\n\n## 🆕 Geradores\n\nGeradores são funções que iniciam uma _goroutine_ para escrever uma lista de valores em um canal que é retornado para quem acionou a função.\n\nNo exemplo, uma sequência de números inteiros é gerada e enviada para um canal.\n\nA função principal (_main_) irá realizar a leitura do canal e imprimir os valores. Essa é uma característica interessante sobre canais, quando utilizados com o _range_, a iteração continuará até que o canal seja fechado.\n\n```go\npackage main\n\nimport \"fmt\"\n\nfunc sequenciaNumeros(inicial, final int) \u003c-chan int {\n\tsaida := make(chan int)\n\tgo func() {\n\t\tfor i := inicial; i \u003c= final; i++ {\n\t\t\tsaida \u003c- i\n\t\t}\n\t\t// após gerar todos os valores, fecha o canal\n\t\tclose(saida)\n\t}()\n\treturn saida\n}\n\nfunc main() {\n\tvalores := sequenciaNumeros(1, 1000)\n\tfor valor := range valores {\n\t\tfmt.Printf(\"valor: %v\\n\", valor)\n\t}\n}\n\n```\n\n## 🚧 Trabalhador (worker)\n\nUm trabalhador é uma _goroutine_ que recebe valores de um canal e os processa.\n\nNo exemplo, valores inteiros são enviados pela função principal (main) através do canal de entrada e processados por um trabalhador.\n\nÉ possível criar vários trabalhadores para processarem um mesmo canal.\n\n```go\npackage main\n\nimport \"fmt\"\n\nfunc trabalhador(entrada \u003c-chan int) {\n\tfor valor := range entrada {\n\t\tfmt.Printf(\"valor: %v\\n\", valor)\n\t}\n}\n\nfunc main() {\n\tentrada := make(chan int)\n\t// Um trabalhador é iniciado e aguarda por valores no canal de entrada\n\tgo trabalhador(entrada)\n\tfor i := 0; i \u003c 10; i++ {\n\t\tentrada \u003c- i\n\t}\n\t// Após ter enviado todos os valores, fecha o canal de entrada\n\t// avisando ao trabalhador que o trabalho terminou\n\tclose(entrada)\n}\n\n```\n\n## 👷‍♂️👷‍♀️ Grupo de Trabalhadores (pool of workers)\n\nA piscina de marmotinhas (carinhosamente chamada pela minha esposa) é uma coleção de _goroutines_ que ficam esperando tarefas serem atribuídas a elas. Quando a _goroutine_ finaliza a tarefa que foi atribuída, se torna disponível novamente para execução de uma nova tarefa.\n\nNo exemplo, um grupo de n trabalhadores aguarda a chegada de valores pelo canal de entrada. Cada trabalhador executa seu processamento e envia o resultado por um canal.\n\nUm canal de sinalização é utilizado para indicar que todos os trabalhadores terminaram.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n)\n\n// trabalhador processa valores recebidos do canal de entrada e envia resultados para o canal de saída.\n// Ele utiliza um canal de sinalização para notificar quando terminar.\nfunc trabalhador(id int, entrada \u003c-chan int, saida chan\u003c- int, terminar chan struct{}) {\n\tfor valor := range entrada {\n\t\tfmt.Printf(\"id: %d processou valor: %v\\n\", id, valor)\n\t\tsaida \u003c- valor * 2\n\t}\n\n\t// Envia uma mensagem para o canal de sinalização ao terminar\n\tfmt.Printf(\"id: %d terminou\\n\", id)\n\tterminar \u003c- struct{}{}\n}\n\nfunc grupoDeTrabalhadores(entrada \u003c-chan int, nTrabalhadores int) chan int {\n\tsaida := make(chan int)\n\tterminar := make(chan struct{}, nTrabalhadores)\n\n\t// Cria e inicia os trabalhadores\n\tfor i := 0; i \u003c nTrabalhadores; i++ {\n\t\tgo trabalhador(i+1, entrada, saida, terminar)\n\t}\n\n\t// Goroutine para fechar o canal de saída quando todos os trabalhadores terminarem\n\tgo func() {\n\t\t// Espera receber sinais de todos os trabalhadores\n\t\tfor i := 0; i \u003c nTrabalhadores; i++ {\n\t\t\t\u003c-terminar\n\t\t}\n\t\tclose(saida)\n\t}()\n\n\treturn saida\n}\n\nfunc sequenciaNumeros(inicial, final int) \u003c-chan int {\n\tsaida := make(chan int)\n\tgo func() {\n\t\tfor i := inicial; i \u003c= final; i++ {\n\t\t\tsaida \u003c- i\n\t\t}\n\t\t// Após gerar todos os valores, fecha o canal\n\t\tclose(saida)\n\t}()\n\treturn saida\n}\n\nfunc main() {\n\t// Produz uma sequência de 10 valores\n\tentrada := sequenciaNumeros(1, 10)\n\t// Um grupo de trabalhadores irá processar esses números\n\tsaida := grupoDeTrabalhadores(entrada, 2)\n\n\t// Somente termina quando todo o trabalho for processado\n\tfor s := range saida {\n\t\tfmt.Println(s)\n\t}\n}\n\n```\n\n## 🧑‍🏭 Pipeline\n\nUm _pipeline_ trabalha recebendo valores de um canal e escrevendo em outro canal, normalmente após realizar alguma transformação no valor.\n\nNo exemplo temos a função `dobro` atuando como um _pipeline_, que irá receber os valores enviados ao canal de entrada retornando os valores transformados.\n\nUm canal pode ser definido como sendo apenas para leitura (`\u003c-chan`) ou apenas para escrita (`chan\u003c-`).\n\nOs valores gerados pelo gerador `sequenciaNumeros` são enviados para o canal de entrada do pipeline e seu valor transformado recebido pelo canal de saída na função principal e é impresso.\n\nVários pipelines poderiam ser encadeados para realizar múltiplas transformações.\n\n```go\npackage main\n\nimport \"fmt\"\n\nfunc dobro(entrada \u003c-chan int) \u003c-chan int {\n\tsaida := make(chan int)\n\tgo func() {\n\t\tfor valor := range entrada {\n\t\t\tsaida \u003c- valor * 2\n\t\t}\n\t\t// Após ter terminado de transformar os valores de entrada,\n\t\t//  fecha o canal de saida\n\t\tclose(saida)\n\t}()\n\treturn saida\n}\n\nfunc sequenciaNumeros(inicial, final int) \u003c-chan int {\n\tsaida := make(chan int)\n\tgo func() {\n\t\tfor i := inicial; i \u003c= final; i++ {\n\t\t\tsaida \u003c- i\n\t\t}\n\t\t// após gerar todos os valores, fecha o canal\n\t\tclose(saida)\n\t}()\n\treturn saida\n}\n\nfunc main() {\n\tfor valor := range dobro(dobro(sequenciaNumeros(1, 10))) {\n\t\tfmt.Printf(\"valor: %v\\n\", valor)\n\t}\n}\n\n```\n\n## ⚗️ Fan-in\n\nUm fan-in copia dados de múltiplos canais de entrada e escreve em um único canal de saída. Normalmente um fan-in só termina quando todos os canais de entrada são fechados.\n\nA função fan-in pode receber vários canais de entrada através de [parâmetros múltiplos](https://gobyexample.com/variadic-functions).\n\nNo exemplo abaixo, enviamos vários geradores como entrada para a função fan-in e nos é retornado um único canal de saída. Internamente, uma _goroutine_ é criada para ler os valores de cada canal de entrada, porém todas escrevem no mesmo canal de saída.\n\nEnvio de mensagem em um canal fechado causa um erro (_panic_), por isso é importante garantir que todos os canais de entrada estejam fechados antes de fechar o canal de saída. Utilizamos um canal de sinalização para indicar que todos os canais de entrada foram processados.\n\nRepare que temos uma _goroutine_ que aguarda um sinal indicando que todas as entradas foram consumidas (wg.Wait), finalizando assim o canal de saída.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n)\n\n// fanin combina vários canais de entrada em um único canal de saída.\n// Utiliza um canal de sinalização para saber quando todos os canais de entrada foram processados.\nfunc fanin(entradas ...\u003c-chan int) \u003c-chan int {\n\tsaida := make(chan int)\n\n\tgo func() {\n\t\t// Número de canais de entrada\n\t\tn := len(entradas)\n\t\t// Canal de controle para quando todos os canais de entrada terminarem\n\t\tcanalTermino := make(chan struct{}, n)\n\n\t\tfor _, c := range entradas {\n\t\t\tgo func(c \u003c-chan int) {\n\t\t\t\tfor n := range c {\n\t\t\t\t\tsaida \u003c- n\n\t\t\t\t}\n\t\t\t\t// Notifica que este canal foi processado\n\t\t\t\tcanalTermino \u003c- struct{}{}\n\t\t\t}(c)\n\t\t}\n\n\t\t// Quando todos os canais de entrada terminarem, fecha o canal de saída\n\t\tgo func() {\n\t\t\tfor i := 0; i \u003c n; i++ {\n\t\t\t\t\u003c-canalTermino\n\t\t\t}\n\t\t\tclose(saida)\n\t\t}()\n\t}()\n\n\treturn saida\n}\n\n// sequenciaNumeros cria um canal que envia uma sequência de números de inicial a final.\nfunc sequenciaNumeros(inicial, final int) \u003c-chan int {\n\tsaida := make(chan int)\n\tgo func() {\n\t\tfor i := inicial; i \u003c= final; i++ {\n\t\t\tsaida \u003c- i\n\t\t}\n\t\tclose(saida)\n\t}()\n\treturn saida\n}\n\nfunc main() {\n\t// Combina três canais de sequência em um único canal\n\tcanal := fanin(\n\t\tsequenciaNumeros(1, 10),\n\t\tsequenciaNumeros(11, 20),\n\t\tsequenciaNumeros(21, 30),\n\t)\n\n\t// Lê e imprime os valores do canal combinado\n\tfor valor := range canal {\n\t\tfmt.Printf(\"valor: %v\\n\", valor)\n\t}\n}\n\n```\n\n## 📣 Fan-out\n\nUm fan-out copia dados de um canal de entrada para múltiplos canais de saída.\n\nNo exemplo, uma sequência de números é gerada e enviada para múltiplos canais de saída. Estes canais possuem seus respectivos trabalhadores que irão fazer o processamento do valor.\n\nEsta implementação de fan-out tenta garantir a entrega de todas as mensagens utilizando um agrupador (WaitGroup) para aguardar a publicação dos valores em todos os canais de saída. A publicação é feita em sua própria _goroutine_ e conta também com um mecanismo (_timer_) de forma a prevenir o bloqueio caso algum canal de saída não consiga consumir a mensagem. As mensagens não consumidas são descartadas.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n)\n\n// publicar tenta enviar um valor para o canal `saida` e utiliza um contexto com timeout\n// para garantir que a operação não dure mais do que o tempo especificado.\nfunc publicar(ctx context.Context, saida chan\u003c- int, valor int, controle chan\u003c- struct{}) {\n\t// Cria um contexto com timeout de 1 segundo\n\tctx, cancel := context.WithTimeout(ctx, 1*time.Second)\n\tdefer cancel()\n\n\tselect {\n\tcase \u003c-ctx.Done():\n\t\t// Se o contexto expirar antes do envio, não faz nada\n\tcase saida \u003c- valor:\n\t\t// Se o valor for enviado com sucesso antes do timeout\n\t}\n\tcontrole \u003c- struct{}{}\n}\n\nfunc fanout(entrada \u003c-chan int, saidas ...chan\u003c- int) {\n\t// Canal para controlar o término das publicações\n\tcontrole := make(chan struct{}, len(saidas)*2) // capacidade para controle de todas as publicações\n\n\tfor valor := range entrada {\n\t\t// Publica o valor de entrada em todas as saídas\n\t\tfor _, saida := range saidas {\n\t\t\tgo publicar(context.Background(), saida, valor, controle)\n\t\t}\n\t\t// Aguarda o término de todas as publicações\n\t\tfor i := 0; i \u003c len(saidas); i++ {\n\t\t\t\u003c-controle\n\t\t}\n\t}\n\t// Como a entrada foi consumida, fecha os canais de saída\n\tfor _, saida := range saidas {\n\t\tclose(saida)\n\t}\n}\n\nfunc sequenciaNumeros(inicial, final int) \u003c-chan int {\n\tsaida := make(chan int)\n\tgo func() {\n\t\tfor i := inicial; i \u003c= final; i++ {\n\t\t\tsaida \u003c- i\n\t\t}\n\t\t// após gerar todos os valores, fecha o canal\n\t\tclose(saida)\n\t}()\n\treturn saida\n}\n\nfunc trabalhador(in \u003c-chan int, id int, controle chan\u003c- struct{}) {\n\tfor v := range in {\n\t\tfmt.Println(\"id: \", id, \" valor: \", v)\n\t}\n\tcontrole \u003c- struct{}{}\n}\n\nfunc main() {\n\tsaida1 := make(chan int)\n\tsaida2 := make(chan int)\n\n\t// Canal para aguardar o término dos trabalhadores\n\tcontrole := make(chan struct{}, 2)\n\n\t// Inicia trabalhadores\n\tgo trabalhador(saida1, 1, controle)\n\tgo trabalhador(saida2, 2, controle)\n\n\t// Distribui a sequência de números para os canais de saída\n\tfanout(sequenciaNumeros(1, 10), saida1, saida2)\n\n\t// Aguarda o término dos trabalhadores\n\tfor i := 0; i \u003c 2; i++ {\n\t\t\u003c-controle\n\t}\n}\n\n```\n\n## 🪟 Janela deslizante\n\nUma janela deslizante (sliding window) é utilizada para prevenir que um leitor lento trave um escritor rápido. Ela funciona deslizando sobre os dados. A ordem de entregas é garantida, porém dados antigos podem ser descartados se o consumidor for muito lento.\n\nNo exemplo, uma sequência de números é gerada, porém nosso consumidor é mais lento que o produtor, logo à medida que a janela desliza os valores antigos são descartados.\n\nPara fazer a janela deslizante, utilizamos um buffer, que possui um tamanho fixo. Utilizamos uma técnica de seleção (select) onde, caso o canal de saída seja lido, enviamos o valor para o consumidor e o removemos do buffer. Caso o canal de entrada seja lido, o valor é adicionado ao buffer.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\nfunc janelaDeslizante(saida chan\u003c- interface{}, entrada \u003c-chan interface{}, tamanho int) {\n\tbuffer := make(chan interface{}, tamanho)\n\tdefer close(saida)\n\n\t// Lógica de leitura do produtor\n\tgo func() {\n\t\tdefer close(buffer)\n\t\tfor val := range entrada {\n\t\t\t// Se o buffer estiver cheio, descarta o mais antigo\n\t\t\tif len(buffer) == tamanho {\n\t\t\t\t\u003c-buffer\n\t\t\t\tfmt.Printf(\"Janela Deslizante: Buffer cheio, descartou valor antigo para adicionar %v.\\n\", val)\n\t\t\t}\n\t\t\tbuffer \u003c- val\n\t\t}\n\t}()\n\n\t// Lógica de envio para o consumidor\n\tfor val := range buffer {\n\t\tsaida \u003c- val\n\t\tfmt.Printf(\"Janela Deslizante: Enviou %v para o consumidor.\\n\", val)\n\t}\n}\n\n// O resto do código permanece o mesmo.\nfunc sequenciaNumeros(inicial, final int) \u003c-chan interface{} {\n\tsaida := make(chan interface{})\n\tgo func() {\n\t\tfor i := inicial; i \u003c= final; i++ {\n\t\t\tsaida \u003c- i\n\t\t\tfmt.Printf(\"Produtor: Enviou %d\\n\", i)\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t}\n\t\tclose(saida)\n\t}()\n\treturn saida\n}\n\nfunc leitorLento(in \u003c-chan interface{}) {\n\tfor val := range in {\n\t\tfmt.Printf(\"Consumidor: Recebeu %v\\n\", val)\n\t\ttime.Sleep(4 * time.Second)\n\t}\n}\n\nfunc main() {\n\tvalores := sequenciaNumeros(1, 10)\n\tsaida := make(chan interface{})\n\tgo leitorLento(saida)\n\tjanelaDeslizante(saida, valores, 3)\n\tfmt.Println(\"Fim da execução.\")\n}\n```\n\n## 🧑‍🤝‍🧑 Processamento em lote (batch processing)\n\nUm processamento em lote (batch processing) é usado quando uma _goroutine_ gera itens um por um, mas o consumidor deseja processar os itens em blocos. Normalmente, um canal de conclusão é usado para notificar o escritor que o item foi processado. Um canal de descarga pode ser usado para forçar que o buffer seja enviado antes que ele esteja cheio.\n\nExemplo: Ao invés de salvar cada item no banco de dados assim que ele é recebido, é possível utilizar um buffer de 100 itens ou 100ms e salvar os itens em uma única requisição.\n\nNo exemplo, quando a terceira requisição (req) é enviada, o buffer percebe que ele está cheio e envia os dados para o canal de saída.\n\nHá um canal que permite enviar os dados antes que o buffer esteja cheio, chamado `descarga`.\n\nQuando o canal de entrada é fechado, mas ainda há itens no buffer, o buffer é enviado para o canal de saída.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n)\n\ntype req struct {\n\tvalor int\n}\n\nfunc processar(lote []req) {\n\tfmt.Println(\"processando lote com valores: \", lote)\n}\n\nfunc processadorLotes(entrada \u003c-chan []req) chan bool {\n\tpronto := make(chan bool)\n\tgo func() {\n\t\tfor lote := range entrada {\n\t\t\tprocessar(lote)\n\t\t}\n\t\tpronto \u003c- true\n\t}()\n\treturn pronto\n}\n\nfunc processamentoLotes(entrada \u003c-chan req, descarga \u003c-chan struct{}, tamanhoLote int) chan []req {\n\tsaida := make(chan []req)\n\tgo func() {\n\t\tdefer close(saida)\n\t\tbuf := make([]req, 0, tamanhoLote)\n\n\t\tfor {\n\t\t\tselect {\n\t\t\t// enquanto houver itens para processar\n\t\t\tcase r, ok := \u003c-entrada:\n\t\t\t\tif !ok {\n\t\t\t\t\t// envia o que tiver no buffer antes de sair\n\t\t\t\t\tif len(buf) \u003e 0 {\n\t\t\t\t\t\tsaida \u003c- buf\n\t\t\t\t\t}\n\t\t\t\t\t// para o loop quando o canal de entrada for fechado\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Adiciona o item no buffer\n\t\t\t\tbuf = append(buf, r)\n\t\t\t\t// se o buffer estiver cheio, descarrega\n\t\t\t\tif len(buf) == tamanhoLote {\n\t\t\t\t\tsaida \u003c- buf\n\t\t\t\t\tbuf = make([]req, 0, tamanhoLote)\n\t\t\t\t}\n\n\t\t\t// Se receber um sinal de descarga, descarrega o que tiver no buffer\n\t\t\tcase \u003c-descarga:\n\t\t\t\tif len(buf) \u003e 0 {\n\t\t\t\t\tsaida \u003c- buf\n\t\t\t\t\tbuf = make([]req, 0, tamanhoLote)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\treturn saida\n}\n\nfunc main() {\n\tentrada := make(chan req)\n\tdescarga := make(chan struct{})\n\n\t// inicia de forma concorrente o processamento em lotes\n\tsaida := processamentoLotes(entrada, descarga, 3)\n\tpronto := processadorLotes(saida)\n\n\tentrada \u003c- req{valor: 1}\n\tentrada \u003c- req{valor: 2}\n\tentrada \u003c- req{valor: 3}\n\n\t// Envia mais dois itens, porém força a descarga\n\t// através de um sinal\n\tentrada \u003c- req{valor: 4}\n\tentrada \u003c- req{valor: 5}\n\tdescarga \u003c- struct{}{}\n\n\t// Envia mais dois itens, não o suficiente para descarregar\n\t// o lote.\n\tentrada \u003c- req{valor: 6}\n\tentrada \u003c- req{valor: 7}\n\t// Eles serão processados mesmo assim.\n\n\tclose(entrada)\n\n\t// Aguarda todo o processamento do processador de lotes\n\t// antes de encerrar o programa\n\t\u003c-pronto\n\tfmt.Println(\"Fim do programa.\")\n}\n```\n\n## 🎫 Sistema de ticket\n\nUm sistema de ticket é usado para controlar quando um determinado trabalho pode ser executado, normalmente é utilizado para limitar o uso de um recurso sobre um período de tempo.\n\nExemplo: Uma API pode ser acionada apenas 15 vezes em um período de 15 minutos. A utilização é medida em blocos de 15 minutos.\n\nNo exemplo, a bilheteria é um sistema de ticket que garante que apenas 15 \"tickets\" sejam processados a cada segundo.\n\nEnviamos através de um canal 30 processamentos a serem feitos, mas o sistema de ticket garante que apenas 15 processamentos sejam executados por segundo.\n\nComo pode ser visto, o trabalhador fica bloqueado até que um ticket seja enviado através do canal.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\ntype (\n\tTrabalho func()\n\tticket   int\n)\n\nfunc trabalhador(tickets \u003c-chan ticket, work \u003c-chan Trabalho) {\n\tfor w := range work {\n\t\t\u003c-tickets // espera por um ticket\n\t\tw()       // executa um trabalho\n\t}\n}\n\nfunc bilheteria(tickets chan\u003c- ticket, timeout time.Duration, nTickets int) {\n\tfor {\n\t\tfor i := 0; i \u003c nTickets; i++ {\n\t\t\ttickets \u003c- ticket(i)\n\t\t}\n\n\t\t// espera até que mais tickets possam ser emitidos\n\t\t\u003c-time.After(timeout)\n\t}\n}\n\nfunc main() {\n\ttickets := make(chan ticket)\n\ttrabalhos := make(chan Trabalho)\n\n\tgo bilheteria(tickets, 1*time.Second, 10)\n\tgo trabalhador(tickets, trabalhos)\n\n\tfor i := 0; i \u003c= 30; i++ {\n\n\t\ttrabalhos \u003c- func() {\n\t\t\tfmt.Println(\"processando ticket\")\n\t\t}\n\t\tfmt.Println(\"trabalho \", i, \" enviado\")\n\t}\n\n\tclose(trabalhos)\n\tclose(tickets)\n}\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcassiobotaro%2Fconcorrencia-go","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcassiobotaro%2Fconcorrencia-go","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcassiobotaro%2Fconcorrencia-go/lists"}