Published
- 2 min read
sync.Semaphore
Background
sem.Acquire(context.Background(), 1)
Concurrency helps us complete tasks faster, however, sometimes too much concurrency breaks things.
Semaphore allows us to control our concurrency.
ELI5 Why we need it
Imagine trying to cross a shaky, old wooden bridge. Experts have verified it to be safe for 10 people to be crossing at the same time, however, what happens if 100 people cross the bridge at the same time? It will very likely break.
In this case, each person crossing the bridge is 1 concurrency and we want to limit concurrency to max 10.
How to use it
First, let us define a function to mimic a person crossing a bridge:
// BoardBus mimics the time taken for a person to board a bus
func crossBridge() {
time.Sleep(1*time.Second)
}
Now, we can test how long it takes 100 people to cross a bridge that only allows 10 people at any time.
func simulateHundredPeopleCrossBridge() {
ts:= time.Now()
sem := semaphore.NewWeighted(10) // declare semaphore with 10 slots
wg := sync.WaitGroup{}
for i := 0 ; i< 100 ; i++ {
wg.Add(1)
go func () {
sem.Acquire(context.Background(), 1) // try to get 1 slot
crossBridge()
wg.Done()
sem.Release(1) // release 1 slot
}()
}
wg.Wait()
fmt.Println(time.Since(ts).Seconds())
}
// Output: 10.001244
Roughly 10 seconds as expected. Let’s list the key components here:
semaphore.NewWeighted(10)
sem.Acquire(context.Background(), 1)
sem.Release(1)
semaphore.NewWeighted(10)
This initializes a semaphore with 10 slots, which we will use to control the number of concurrency.
sem.Acquire(context.Background(), 1)
Before any person crosses a bridge, he/she will have to acquire a slot from the semaphore before crossing. If there are no slots, the person(goroutine) will wait until they get 1.
sem.Release(1)
After crossing a bridge, the person will return the slot back to the semaphore.
Rabbit Hole
Try implementing semaphores using go channels.
Alright, that is all for today. Let’s get one post smarter every day, and see you tomorrow! sem.Release(1)