Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
35: Common Concurrency Patterns: Fan-In/Fan-Out
Imagine you're running a busy coffee shop. You've got one person at the register taking orders. If that person also had to steam the milk, pull the espresso shots, and foam the latte, the line would stretch out the door. That's a bottleneck.
Instead, the register person (the source) writes the order on a cup and places it on a rail. Now, you have three baristas (the workers) who all grab cups from that same rail as fast as they can. This is Fan-Out: taking one stream of work and spreading it across multiple workers to get it done faster. But you can't have three different people handing drinks to customers at three different counters; that's chaos. So, all three baristas place the finished drinks on a single pick-up counter. This is Fan-In: gathering multiple streams of results back into one single channel for processing.
Spreading the Load with Fan-Out
In Go, Fan-Out is surprisingly simple. You create a channel to hold your "jobs" and then start multiple goroutines that all read from that same channel. Because Go channels are thread-safe, you don't have to worry about two workers grabbing the same job; the runtime handles that for you.
I've found that the trick here is deciding how many workers to spawn. If you're doing CPU-heavy work, matching the number of workers to runtime.NumCPU() is a good bet. If you're doing I/O (like hitting an API), you can usually push that number much higher.
type Result struct {
url string
status int
}
func worker(urls <-chan string, results chan<- Result) {
for url := range urls {
// Simulate an HTTP request
status := 200
results <- Result{url: url, status: status}
}
}
func main() {
urls := make(chan string, 10)
results := make(chan Result, 10)
// Fan-out: Start 3 workers
for i := 0; i < 3; i++ {
go worker(urls, results)
}
// Send work
sites := []string{"google.com", "golang.org", "github.com", "stackoverflow.com"}
for _, s := range sites {
urls <- s
}
close(urls) // Tell workers there's no more work
}
Gathering the Results with Fan-In
The code above has a problem: the results channel is being fed by three different goroutines. If we just try to range over it, we might hit a deadlock or miss data because we don't know when all the workers are actually finished. We need a way to "merge" these streams.
The most robust way to handle Fan-In is using a sync.WaitGroup. I usually wrap the worker management in a separate goroutine so the main thread can just listen to the merged results without blocking.
func merge(cs ...<-chan Result) <-chan Result {
var wg sync.WaitGroup
out := make(chan Result)
// Function to move values from a source channel to the output channel
output := func(c <-chan Result) {
for n := range c {
out <- n
}
wg.Done()
}
wg.Add(len(cs))
for _, c := range cs {
go output(c)
}
// This is the secret sauce: close the out channel
// only after ALL source channels are drained
go func() {
wg.Wait()
close(out)
}()
return out
}
Putting it Together: The Pipeline
When you combine these, you get a powerful pipeline. You produce work, fan it out to workers, and fan the results back in. The key is the ownership of the channels. The producer closes the jobs channel, the workers finish their loops, and the merge function closes the results channel once the WaitGroup hits zero.
It looks like a lot of "plumbing" at first, but this pattern is exactly how high-performance Go services handle massive streams of data. You're essentially building a conveyor belt where the middle section can be widened (more workers) depending on how slow the processing step is compared to the input step.
📋 Practical Task
Exercise: Building a Concurrent Website Status Scanner
Your task is to create a program that checks the availability of a list of websites concurrently. You'll need to implement both the Fan-Out and Fan-In patterns to ensure the program is efficient and shuts down cleanly.
Requirements:
- Create a slice of at least 10 URLs.
- Implement a
jobschannel that distributes these URLs to 5 worker goroutines (Fan-Out). - Each worker should simulate a network request (use
time.Sleepfor a random duration between 100ms and 500ms) and return aResultstruct containing the URL and a fake status code. - Implement a
mergefunction (Fan-In) that collects results from all workers into a single output channel. - The
mainfunction must range over the final results channel and print each result, exiting only after all websites have been processed. - Ensure there are no goroutine leaks; all channels must be closed appropriately.
There are no comments for now.