Na contrapressão o consumidor lento fazia o produtor esperar, e nada se perdia.

Mas nem todo valor merece esperar. Se o produtor é um sensor de posição e o consumidor atrasou quatro segundos, a leitura de quatro segundos atrás não serve mais. Interessa a mais nova.

O que é uma janela deslizante Link para o cabeçalho

Uma janela deslizante guarda só os n valores mais recentes. Quando chega um novo e a janela está cheia, o mais antigo sai para dar lugar.

Ela também aparece como drop-oldest buffer. Não confunda com ring buffer, que é um jeito de guardar os dados. A janela é a regra de descarte.

Uma única gorrotina cuida da fila e reage no select ao que acontecer primeiro:

func janelaDeslizante(entrada <-chan int, saida chan<- int, tamanho int) {
	defer close(saida)
	var fila []int

	for entrada != nil || len(fila) > 0 {
		// o case de envio só existe quando há algo na fila
		var envio chan<- int
		var cabeca int
		if len(fila) > 0 {
			envio = saida
			cabeca = fila[0]
		}

		select {
		case valor, ok := <-entrada:
			if !ok {
				// entrada fechada: daqui em diante só esvazia a fila
				entrada = nil
				continue
			}
			if len(fila) == tamanho {
				fmt.Printf("Janela Deslizante: Buffer cheio, descartou %v para adicionar %v.\n", fila[0], valor)
				fila = fila[1:]
			}
			fila = append(fila, valor)

		case envio <- cabeca:
			fmt.Printf("Janela Deslizante: Enviou %v para o consumidor.\n", cabeca)
			fila = fila[1:]
		}
	}
}

A main liga um produtor que manda um valor por segundo a um consumidor que leva quatro segundos em cada um, com uma janela de três:

valores := sequenciaNumeros(ctx, 1, 10)
saida := make(chan int)
pronto := make(chan struct{})
go leitorLento(saida, pronto)
janelaDeslizante(valores, saida, 3)
<-pronto
$ go run .
Consumidor: Recebeu 1
Consumidor: Recebeu 2
Janela Deslizante: Buffer cheio, descartou 3 para adicionar 6.
Janela Deslizante: Buffer cheio, descartou 4 para adicionar 7.
Janela Deslizante: Buffer cheio, descartou 5 para adicionar 8.
Consumidor: Recebeu 6
Janela Deslizante: Buffer cheio, descartou 7 para adicionar 10.
Consumidor: Recebeu 8
Consumidor: Recebeu 9
Consumidor: Recebeu 10

O consumidor viu 1, 2, 6, 8, 9 e 10. Os outros quatro saíram pela janela enquanto ele estava ocupado, e o produtor nunca esperou.

Preste atenção que a ordem se manteve. O que a janela perde é valor, não sequência.

Mas que canal nil é esse? Link para o cabeçalho

Olhe o var envio chan<- int. Quando a fila está vazia, ele fica nil, e um case com canal nil nunca é escolhido pelo select. Ou seja, o case de envio some enquanto não há o que enviar.

É o mesmo truque do fan-in com select, e ele aparece de novo quando a entrada fecha. O entrada = nil desliga o case de leitura, e o laço continua só para esvaziar a fila.

Hummm 🤔 E se eu deixar o case de envio sempre ligado, mandando saida <- cabeca direto?

Rodei 20 vezes aqui. Em 12 delas o consumidor recebeu um 0 que nunca entrou na janela, porque cabeca é zero quando a fila está vazia. E às vezes nem isso:

panic: runtime error: slice bounds out of range [1:0]

É o fila = fila[1:] rodando com a fila vazia.

Mas por que uma gorrotina só? Link para o cabeçalho

Repare que produtor e consumidor nunca tocam na fila. Só a gorrotina da janela lê e escreve nela, então não tem disputa e não precisa de mutex.

Uma versão anterior deste exemplo usava um canal com buffer compartilhado por duas gorrotinas, e tinha uma corrida que de vez em quando travava o programa. Isto tem nome, gorrotina dona do estado, e ganha um post só para ela mais para frente na série.

De onde veio o exemplo Link para o cabeçalho

O código está na pasta janelas_deslizantes do concorrencia-go. O teste de lá dispensa o relógio: manda cinco valores sem ninguém ler a saída, e confere que a janela de três descartou o 1 e o 2.

Lembrando que o fila[1:] não libera memória na hora. O array de apoio continua lá até um append realocar, o que não importa para uma janela de três, mas importa para uma de três milhões.

No próximo vem a requisição e resposta, em que a mensagem carrega o canal pelo qual quem pediu espera a resposta.

Então é isso, pessoal!

Até a próxima!

{}’s