Concurrency Patterns: Fan-in and Fan-out in Go
Introduction
Concurrency patterns are essential in modern programming to efficiently manage parallel tasks. In Go, two important concurrency patterns are Fan-in and Fan-out. These patterns help manage and coordinate multiple goroutines effectively.
Fan-out
Fan-out refers to the process of starting multiple concurrent tasks from a single point. In Go, this often means spawning multiple goroutines to perform tasks concurrently.
Example:
func main() {
jobs := make(chan int, 10)
results := make(chan int, 10)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= 9; a++ {
<-results
}
}
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "started job", j)
time.Sleep(time.Second)
fmt.Println("worker", id, "finished job", j)
results <- j * 2
}
}
In the example above, we have a function worker that takes jobs from the jobs channel. We spawn three worker goroutines to process jobs concurrently, demonstrating the Fan-out pattern.
Fan-in
Fan-in is the opposite of Fan-out. It involves collecting results from multiple concurrent tasks into a single point. In Go, this typically means gathering output from multiple goroutines into a single channel.
Example:
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(time.Second * 1)
c1 <- "one"
}()
go func() {
time.Sleep(time.Second * 2)
c2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("Received", msg1)
case msg2 := <-c2:
fmt.Println("Received", msg2)
}
}
}
In this example, we have two goroutines that send messages to channels c1 and c2. The select statement is used to receive messages from both channels, demonstrating the Fan-in pattern.
Combining Fan-in and Fan-out
Often, you will need to combine both Fan-in and Fan-out patterns to efficiently manage concurrent tasks. This involves spawning multiple goroutines (Fan-out) and then collecting their results (Fan-in).
Example:
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= 5; a++ {
fmt.Println(<-results)
}
}
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "started job", j)
time.Sleep(time.Second)
fmt.Println("worker", id, "finished job", j)
results <- j * 2
}
}
Here, jobs are distributed among three workers (Fan-out), and their results are collected in the results channel (Fan-in). This pattern is useful for parallel processing tasks and aggregating their results efficiently.
Conclusion
Understanding and implementing Fan-in and Fan-out patterns in Go can significantly enhance the efficiency and scalability of your programs. These patterns allow you to manage multiple concurrent tasks and aggregate their results seamlessly.
