Definição
Consideramos dois processos, chamados de “produtor” e “consumidor”, respectivamente. O produtor é um processo cíclico e cada vez que passa pelo seu ciclo produz uma determinada porção de informação, que deve ser processada pelo consumidor. O consumidor também é um processo cíclico e cada vez que passa pelo seu ciclo pode processar a próxima porção de informação, tal como foi produzida pelo produtor. Um exemplo simples é dado por um processo computacional, que produz como “porções de informação” imagens de cartões perfurados a serem perfurados por um cartão perfurado, que desempenha o papel do consumidor.[1]
Explicação
Um produtor cria itens e os armazena em uma estrutura de dados, enquanto um consumidor remove os itens dessa estrutura e os processa.
Se o consumo for maior que a produção o buffer(estrutura de dados) esvazia, e o consumidor não tem o que consumir
Se o consumo for menor que a produção o buffer enche, e o produtor não consegue adicionar mais itens. Esse é um problema clássico chamado de buffer limitado.
Contextualização do Problema
Suponha que temos um produtor que publica um e-mail no buffer, e um consumidor que consome o e-mail do buffer e exibe uma mensagem informando que foi enviado um e-mail com a nova senha de acesso para o e-mail informado.
Implementação em go
package main
import (
"fmt"
"os"
"strconv"
"sync"
"time"
)
type buffer struct {
items []string
mu sync.Mutex
}
func (buff *buffer) add(item string) {
buff.mu.Lock()
defer buff.mu.Unlock()
if len(buff.items) < 5 {
buff.items = append(buff.items, item)
// fmt.Println("Foi adicionado o item " + item)
} else {
fmt.Println("O Buffer não pode armazenar nenhum item mais está com a capacidade máxima")
os.Exit(0)
}
}
func (buff *buffer) get() string {
buff.mu.Lock()
defer buff.mu.Unlock()
if len(buff.items) == 0 {
return ""
}
target := buff.items[0]
buff.items = buff.items[1:]
return target
}
var wg sync.WaitGroup
func main() {
buff := buffer{}
wg.Add(2)
go producer(&buff)
go consumer(&buff)
wg.Wait()
}
func producer(buff *buffer) {
defer wg.Done()
for index := 1; ; index++ {
str := strconv.Itoa(index) + "@email.com"
buff.add(str)
time.Sleep(5 * time.Millisecond) // Adiciona um pequeno atraso para simular produção
}
}
func consumer(buff *buffer) {
defer wg.Done()
for {
data := buff.get()
if data != "" {
fmt.Println("Enviado um email com a nova senha de acesso para: " + data)
}
}
}
Explicando a implementação
- Primeiro, criamos uma estrutura chamada buffer, que contém um array de strings chamado items e um mecanismo de controle do tipo mutex, chamado mu, para gerenciar o acesso concorrente.
- Temos duas funções: uma chamada add, que basicamente adiciona um item ao buffer, desde que haja espaço disponível, já que a capacidade do buffer é de apenas 5 itens; e outra chamada get, que, se houver itens no buffer, retorna o primeiro elemento e remove esse elemento do buffer.
- O Producer basicamente pega o index do loop e o concatena em uma string chamada str, que contém o índice e um domínio de e-mail fictício, e adiciona no buffer. Foi adicionado um intervalo de tempo para simular um atraso.
- O Consumer solicita ao buffer um item, caso tenha ao menos um item. Em seguida, o Consumer exibe uma mensagem na tela informando que foi enviado um e-mail com a nova senha de acesso para o item que foi publicado publicado no buffer.
Link do código: https://github.com/jcelsocosta/race_condition/blob/main/producerconsumer/buffer/producerconsumer.go
Referência
Bibliografia
https://www.cin.ufpe.br/~cagf/if677/2015-2/slides/08_Concorrencia%20(Jorge).pdf
Top comments (1)
Ótima explicação!