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

<span class="c">// buffered channel, one slot for every goroutine</span>
<span class="c">// send side can complete without receive (non-blocking)</span>
<span class="n">ch</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="k">chan</span> <span class="kt">string</span><span class="p">,</span> <span class="n">emps</span><span class="p">)</span>

<span class="c">// max number of RUNNING goroutines at any given time</span>
<span class="c">// g := runtime.NumCPU()</span>
<span class="n">g</span> <span class="o">:=</span> <span class="m">2</span>
<span class="c">// buffered channel, based on the max number of the goroutines in RUNNING state</span>
<span class="c">// added to CONTROL the number of goroutines in RUNNING state</span>
<span class="n">sem</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="k">chan</span> <span class="kt">bool</span><span class="p">,</span> <span class="n">g</span><span class="p">)</span>

<span class="k">for</span> <span class="n">e</span> <span class="o">:=</span> <span class="m">0</span><span class="p">;</span> <span class="n">e</span> <span class="o">&lt;</span> <span class="n">emps</span><span class="p">;</span> <span class="n">e</span><span class="o">++</span> <span class="p">{</span>
    <span class="c">// create 10 goroutines in the RUNNABLE state</span>
    <span class="c">// one for each employee</span>
    <span class="k">go</span> <span class="k">func</span><span class="p">(</span><span class="n">emp</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>

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

            <span class="c">// once work is done, signal on ch channel</span>
            <span class="n">ch</span> <span class="o">&lt;-</span> <span class="s">"paper"</span>
            <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"employee : sent signal : "</span><span class="p">,</span> <span class="n">emp</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="c">// once all work is done pull the value from the `sem` channel</span>
        <span class="c">// give place to another goroutine to do the work</span>
        <span class="c">// e.g. employee stands up and free up seat for another employee</span>
        <span class="o">&lt;-</span><span class="n">sem</span>
    <span class="p">}(</span><span class="n">e</span><span class="p">)</span>
<span class="p">}</span>

<span class="c">// wait for all employee work to be done</span>
<span class="k">for</span> <span class="n">emps</span> <span class="o">&gt;</span> <span class="m">0</span> <span class="p">{</span>
    <span class="c">// receive signal sent from the employee</span>
    <span class="n">p</span> <span class="o">:=</span> <span class="o">&lt;-</span><span class="n">ch</span>

    <span class="n">emps</span><span class="o">--</span>
    <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">p</span><span class="p">)</span>
    <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"manager : received signal : "</span><span class="p">,</span> <span class="n">emps</span><span class="p">)</span>

<span class="p">}</span>
Enter fullscreen mode Exit fullscreen mode

}

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

Top comments (0)