Go Concurrency Patterns: Diving into Fan-in and Fan-out

One of the primary reasons developers love Go is its built-in concurrency support. When discussing concurrency in Go, two core patterns emerge: Fan-in and Fan-out. These concepts help in building scalable and efficient systems. Let's dive into what these patterns mean and how they're implemented in Go.

What is Concurrency?

Concurrency is about dealing with multiple tasks at once. It's about structuring your program to handle multiple tasks independently, thereby making your application more efficient. Go uses goroutines, which are lightweight threads managed by the Go runtime, to achieve concurrency.

Fan-out

Fan-out refers to the practice of starting multiple goroutines to handle incoming tasks. The main idea is to distribute incoming tasks to multiple handlers (goroutines) to ensure that each handler deals with a manageable number of tasks.

Advantages of Fan-out:

  1. Scalability: By distributing tasks across multiple goroutines, you can efficiently utilize CPU resources and ensure no single goroutine becomes a bottleneck.

  2. Flexibility: It’s easier to scale up or down by adjusting the number of goroutines as per the workload.

Example of Fan-out in Go:

$ go run main.go
package main

import (
	"fmt"
	"time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
	for j := range jobs {
		fmt.Println("worker", id, "processing job", j)
		time.Sleep(time.Second)
		results <- j * 2
	}
}

func main() {
	const numJobs = 5
	jobs := make(chan int, numJobs)
	results := make(chan int, numJobs)

	// Start 3 workers (fan-out)
	for w := 1; w <= 3; w++ {
		go worker(w, jobs, results)
	}

	// Send jobs
	for j := 1; j <= numJobs; j++ {
		jobs <- j
	}
	close(jobs)

	// Receive results
	for a := 1; a <= numJobs; a++ {
		<-results
	}
}

Fan-in

Fan-in is a term used to describe the process of combining multiple results into one channel. By using fan-in, you can simplify how you collect and process data from multiple channels.

Advantages of Fan-in:

  1. Simplicity: Centralizing results into a single channel can simplify further processing.

  2. Efficiency: Managing a single channel can be more efficient than dealing with multiple channels concurrently.

Example of Fan-in in Go:

package main

import (
	"fmt"
)

func producer(ch chan<- int, d int) {
	for i := 0; i < 3; i++ {
		ch <- i + d
	}
	close(ch)
}

func fanIn(input1, input2 <-chan int, output chan<- int) {
	for {
		select {
		case i1, ok1 := <-input1:
			if ok1 {
				output <- i1
			}
		case i2, ok2 := <-input2:
			if ok2 {
				output <- i2
			}
		}
	}
}

func main() {
	output := make(chan int)
	input1 := make(chan int)
	input2 := make(chan int)

	go producer(input1, 0)
	go producer(input2, 10)
	go fanIn(input1, input2, output)

	for n := range output {
		fmt.Println(n)
	}
}

In the above example, fanIn function combines the results from input1 and input2 channels into a single output channel.

In Summary

Fan-in and Fan-out are concurrency patterns that help manage multiple tasks efficiently in Go. While Fan-out spreads tasks across multiple goroutines for parallel processing, Fan-in gathers results from multiple channels into one. Mastering these patterns is crucial for any Go developer aiming to build efficient and scalable concurrent applications.

Previous
Previous

`defer` in Go: More Than Just Delayed Execution

Next
Next

Using Twirp with Go: A Quick Guide