๐ŸŽ“ A1Learner
๐Ÿ”
0 of 28 lessons completed

Go Rate Limiting

Rate limiting caps how many jobs run at the same time. A channel full of tokens sets the cap.

Take a token, run, return it

The channel starts with two tokens. One job takes a token, runs, and puts the token back.

package main

import "fmt"

func main() {
	tokens := make(chan struct{}, 2)
	tokens <- struct{}{}
	tokens <- struct{}{}
	fmt.Println("Tokens before:", len(tokens))
	<-tokens
	fmt.Println("Job ran")
	fmt.Println("Tokens during:", len(tokens))
	tokens <- struct{}{}
	fmt.Println("Tokens after:", len(tokens))
}

Four jobs, two tokens

Four jobs share two tokens. Each job takes a token, writes its slot, and returns the token.

package main

import (
	"fmt"
	"sync"
)

func main() {
	tokens := make(chan struct{}, 2)
	tokens <- struct{}{}
	tokens <- struct{}{}
	results := make([]int, 4)
	var wg sync.WaitGroup
	for i := 0; i < 4; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			<-tokens
			results[id] = (id + 1) * 10
			tokens <- struct{}{}
		}(i)
	}
	wg.Wait()
	for i, r := range results {
		fmt.Printf("Result %d: %d\n", i, r)
	}
}

The burst uses both tokens at once

The first two jobs take both tokens and hold them. The third job waits until a token comes back.

package main

import (
	"fmt"
	"sync"
)

func main() {
	tokens := make(chan struct{}, 2)
	tokens <- struct{}{}
	tokens <- struct{}{}
	results := make([]int, 3)
	started := make(chan int, 3)
	hold := make(chan struct{})
	var wg sync.WaitGroup
	run := func(id int) {
		defer wg.Done()
		<-tokens
		results[id] = (id + 1) * 10
		started <- id
		<-hold
		tokens <- struct{}{}
	}
	wg.Add(1)
	go run(0)
	<-started
	wg.Add(1)
	go run(1)
	<-started
	fmt.Println("Started first:", results[0], results[1])
	fmt.Println("Tokens left:", len(tokens))
	wg.Add(1)
	go run(2)
	fmt.Println("Job 2 waits for a token")
	close(hold)
	wg.Wait()
	fmt.Println("Results:", results[0], results[1], results[2])
	fmt.Println("Tokens back:", len(tokens))
}

Example

package main

import (
	"fmt"
	"sync"
)

func main() {
	tokens := make(chan struct{}, 2)
	tokens <- struct{}{}
	tokens <- struct{}{}
	results := make([]int, 5)
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			<-tokens
			results[id] = (id + 1) * 10
			tokens <- struct{}{}
		}(i)
	}
	wg.Wait()
	for i, r := range results {
		fmt.Printf("Result %d: %d\n", i, r)
	}
	fmt.Println("Tokens back:", len(tokens))
}

Key points

  • Rate limiting caps how many jobs run at once.
  • A token is one pass taken from a channel.
  • A job takes a token before it runs and returns the token after.
  • A channel with two tokens lets at most two jobs run at once.
  • Jobs write results to indexed slots, so main prints in job order.
  • The len function reads how many tokens sit in the channel.

In this example, five jobs share a channel that holds two tokens. A job takes a token before it writes its slot and returns the token after. Main joins all five jobs and prints the slots in job order. The last line reads the channel length and shows both tokens back.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Go Playground

Exercise

A token channel holds two tokens and five jobs want to run. How many jobs can hold a token at the same time?