15.7 Common Channel Patterns: Pipeline, Fan-Out, Fan-In

Right, so we’ve got channels. We know how to send, receive, and close them. But if you just start flinging <- operators around willy-nilly, you’ll end up with a mess of deadlocks that would make a plate of spaghetti look organized. Let’s talk about the actual patterns you use to structure this chaos. These are the blueprints that turn a novelty act into a professional concurrency powerhouse. The Pipeline Think of this less like a Rustic aqueduct and more like an assembly line. One goroutine takes in some raw materials, does a specific task, and passes the semi-finished product to the next goroutine in line. Each stage only cares about receiving from the stage before it and sending to the stage after it.

15.6 Channel as a Semaphore: Bounding Concurrency

Right, so you’ve got channels. They’re for sending data. But sometimes, you don’t actually care about the data. You just care about the signal. You want to control how many of a certain thing can happen at once, like limiting the number of simultaneous database connections or outgoing API calls. This is called bounding concurrency, and it’s a classic job for a semaphore. A semaphore is just a fancy counter that blocks when it hits zero. You can build one trivially with a buffered channel. The capacity of the channel is your concurrency limit. Instead of sending meaningful data, you’ll just send empty structs (struct{}), which are basically tokens that take up zero bytes of memory. It’s the computational equivalent of a “you may proceed” hand signal.

15.5 Closing Channels: Signaling Completion

Alright, let’s talk about closing channels. This is where you move from simply passing data around to signaling that the party’s over and no more data is coming. It’s one of the most powerful, and most frequently bungled, concepts in Go’s concurrency model. Get this right, and your programs become elegant and robust. Get it wrong, and you’ll have goroutines leaking like a sieve or panicking all over the place.

15.4 Ranging Over a Channel Until It's Closed

Right, so you’ve got a channel, you’re sending stuff into it, and you need to get everything back out. You could try to receive in an infinite loop with a select statement that has a default case to bail out, but that’s a great way to either burn CPU cycles or miss the boat entirely. The real, elegant way to do this—the way that actually understands the intent of the channel—is to use a for loop with range.

15.3 Directional Channels: chan<- and <-chan

Right, let’s talk about directional channels. You’ve seen chan int by now, but you might have also seen some weird-looking stuff like chan<- string or <-chan bool and wondered if the Go designers were just messing with you. They weren’t. This is one of those features that seems like a tiny, pedantic detail at first but quickly becomes your best friend for writing clear, robust, and safe concurrent code. It’s basically the type system giving you a free security review.

15.2 Buffered Channels: Asynchronous with Capacity

Right, let’s talk about buffered channels. You’ve met their unbuffered cousins, which are basically high-stakes handoffs: I wait for you, you wait for me. A buffered channel is different. It’s more like a mail slot or a queue at a post office. You can put a certain number of letters in the slot before you have to wait for someone to come and take one out. This introduces a layer of asynchrony into our communication, which is both incredibly useful and a fantastic way to shoot yourself in the foot if you’re not careful.

15.1 Unbuffered Channels: Synchronous Rendezvous

Right, unbuffered channels. Let’s cut through the academic fluff. Think of these not as a “queue” or a “pipe,” but as a handshake. A very strict, very demanding, and perfectly synchronous handshake. I’m talking about two goroutines meeting at a specific point in time to exchange a value. One goroutine extends its hand with a value, and it will stand there, hand out, frozen in time, until another goroutine reaches out and takes it. Conversely, a goroutine that reaches out to take a value will stand there, hand out, until another goroutine is ready to give it one. This isn’t a mailbox; it’s a meeting point.

— joke —

...