Skip to main content
eLearner.app
Module 7 · Lesson 2 of 532/50 in the course~15 min
Module lessons (2/5)

Channels: typed communication

Channels are the idiomatic mechanism for communication between goroutines. The Go philosophy (Rob Pike):

"Don't communicate by sharing memory; share memory by communicating."

A channel is a typed FIFO queue, type-safe and safe for concurrent use. Combined with go, it is the primary building block of Go concurrency.

Create, send, receive

Go
ch := make(chan int)     // canale di int, unbuffered

go func() {
    ch <- 42             // invia (può bloccare)
}()

v := <-ch                // ricevi (può bloccare)
fmt.Println(v)           // 42

Syntax:

  • make(chan T) or make(chan T, N) for buffered.
  • ch <- v sends.
  • v := <-ch receives.
  • v, ok := <-ch receives with a flag (ok = false if the channel is closed AND empty).

Unbuffered: rendezvous

An unbuffered channel (capacity 0) blocks:

  • the sender until a receiver is ready;
  • the receiver until a sender is ready.

It is a synchronous appointment: useful as a "signal" or to guarantee that the value has been delivered.

Buffered: queue up to N

Go
ch := make(chan int, 3)
ch <- 1   // non blocca
ch <- 2   // non blocca
ch <- 3   // non blocca
ch <- 4   // BLOCCA: il buffer è pieno

The sender blocks only when the buffer is full; the receiver only when it is empty.

close and for-range

The sender closes the channel when there are no more values:

Go
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)

for v := range ch {      // termina quando ch è chiuso E vuoto
    fmt.Println(v)
}

for range over a channel iterates until the channel is closed (and drained). It is the cleanest pattern for "consume everything that arrives".

Directionality in signatures

Function parameters can restrict the direction:

Go
func producer(out chan<- int) {   // solo invio
    out <- 1
    close(out)
}

func consumer(in <-chan int) {    // solo ricezione
    for v := range in {
        fmt.Println(v)
    }
}

chan<- T = send-only, <-chan T = receive-only. A bidirectional channel chan T can be passed to either (implicit conversion). It improves clarity and prevents mistakes.

Common patterns (preview)

  • Worker pool: N goroutines consume from jobs <-chan T, produce on results chan<- R.
  • Pipeline: stages connected by channels.
  • Done signal: a chan struct{} channel closed to signal "stop".
Go
done := make(chan struct{})
go func() {
    <-done    // blocca finché qualcuno chiude done
    cleanup()
}()
close(done)   // segnale di stop

chan struct{} is the "signal" channel: zero bytes of payload, just synchronization.

Try it

Exercise#go.m7.l2.e1
Attempts: 0Loading…

Create an unbuffered int channel ch, send 42 from a goroutine, receive in main and print it.

Loading editor…
Show hint

Without a buffer, sender and receiver synchronize.

Solution available after 3 attempts

Exercise#go.m7.l2.e2
Attempts: 0Loading…

Send 1,2,3 over a buffered channel, close it, then print everything with for-range.

Loading editor…
Show hint

`for range` over a channel ends when the channel is closed and drained.

Solution available after 3 attempts

Quiz#go.m7.l2.e3
Ready

What does an UNBUFFERED channel do when the sender sends but no receiver is ready?

Go
ch := make(chan int)
ch <- 1 // ?
Answer options

Recap

  • Channel = typed FIFO queue, safe between goroutines.
  • Unbuffered: rendezvous (sender and receiver synchronize).
  • Buffered: decoupling, blocks only when full/empty.
  • close(ch): only the sender; for range consumes until close.
  • Directionality: chan<- T (send-only), <-chan T (receive-only).
  • chan struct{} as a signal channel for pure synchronization.