DEV Community

Cover image for Go Channel Patterns - Fan Out  Semaphore
b0r
b0r

Posted on

Go Channel Patterns - Fan Out Semaphore

To improve my Go Programming skills and become a better Go engineer, I have recently purchased an excellent on-demand education from Ardan Labs. Materials are created by an expert Go engineer, Bill Kennedy.

I have decide to record my process of learning how to write more idiomatic code, following Go best practices and design philosophies.

This series of posts will describe channel patterns used for orchestration/signaling in Go via goroutines.

Fan Out Semaphore Pattern

The main idea behind Fan Out Semaphore Pattern is to have:

  • everything we had in the Fan Out Pattern:

    • a buffered channel that provides a signaling semantics
    • a goroutine that starts multiple (child) goroutines to do some work
    • a multiple (child) goroutines that do some work and use signaling channel to signal the work is done
  • PLUS the addition of a:

    • new semaphore channel used to restrict the number of child goroutines that can be schedule to run

Fan Out Pattern

Example

In Fan Out Pattern we have multiple employees that have some work to do.

We also have a manager (main goroutine) that waits on that work to be done. Once each employee work is done, employee notifies manager by sending a signal (paper) via communication channel ch.

In Fan Out Semaphore Pattern we have an additional constraint in terms of maximum number of employees that can do work at any given moment.

Explanation

For example, we have 100 employees, but only 10 available free seats in the office space. It doesn't matter that 100 employees are available to do the work when we only have adequate space for 10 employees at any given moment. Other 90 employees have to wait until on of those 10 finish the work and frees the seat.

Good use case for this pattern would be batch processing, where we have some amount of work to do, but we want to limit the number of active executors at any given moment.

Feel free to try the example on Go Playground

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    emps := 10

    // buffered channel, one slot for every goroutine
    // send side can complete without receive (non-blocking)
    ch := make(chan string, emps)

    // max number of RUNNING goroutines at any given time
    // g := runtime.NumCPU()
    g := 2
    // buffered channel, based on the max number of the goroutines in RUNNING state
    // added to CONTROL the number of goroutines in RUNNING state
    sem := make(chan bool, g)

    for e := 0; e < emps; e++ {
        // create 10 goroutines in the RUNNABLE state
        // one for each employee
        go func(emp int) {

            // when goroutine moves from RUNNABLE to RUNNING state
            // send signal/value inside a `sem` channel
            // if `sem` channel buffer is full, this will block
            // e.g. employee takes a seat
            sem <- true
            {
                // simulate the idea of unknown latency (do not use in production)
                // e.g. employee does some work
                time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)

                // once work is done, signal on ch channel
                ch <- "paper"
                fmt.Println("employee : sent signal : ", emp)
            }

            // once all work is done pull the value from the `sem` channel
            // give place to another goroutine to do the work
            // e.g. employee stands up and free up seat for another employee
            <-sem
        }(e)
    }

    // wait for all employee work to be done
    for emps > 0 {
        // receive signal sent from the employee
        p := <-ch

        emps--
        fmt.Println(p)
        fmt.Println("manager : received signal : ", emps)

    }
}
Enter fullscreen mode Exit fullscreen mode

Result

go run main.go

employee : sent signal :  9
paper
manager : received signal :  9
employee : sent signal :  4
paper
manager : received signal :  8
employee : sent signal :  1
paper
manager : received signal :  7
employee : sent signal :  2
paper
manager : received signal :  6
employee : sent signal :  3
paper
manager : received signal :  5
employee : sent signal :  8
paper
manager : received signal :  4
employee : sent signal :  6
paper
manager : received signal :  3
employee : sent signal :  5
paper
manager : received signal :  2
employee : sent signal :  0
paper
manager : received signal :  1
employee : sent signal :  7
paper
manager : received signal :  0
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this article, fan out semaphore channel pattern was described. In addition, simple implementation was provided.

Readers are encouraged to check out excellent Ardan Labs education materials to learn more.

Resources:

  1. Ardan Labs
  2. Cover image by Igor Mashkov from Pexels
  3. Fan out picture

Discussion (0)