Channels in Go

Unless two goroutines can communicate, they can’t coordinate.
Go has a type called a channel that provides communication and synchronization capabilities.
It also has special control structures that build on channels to make concurrent programming easy.
The channel type

In its simplest form the type looks like this:
chan element_type
Given a value of this type, you can send and receive [...]

Unless two goroutines can communicate, they can’t coordinate.
Go has a type called a channel that provides communication and synchronization capabilities.

It also has special control structures that build on channels to make concurrent programming easy.

The channel type

In its simplest form the type looks like this:

chan element_type

Given a value of this type, you can send and receive items of element_type. Channels are a reference type, which means if you assign one chan variable to another, both variables access the same channel. It also means you use make to allocate one:

var c = make(chan int)

The communication operator: <-

The arrow points in the direction of data flow.
As a binary operator, <- sends to a channel:

var c chan int;
c <- 1; // send 1 on c (flowing into c)

As a prefix unary operator, <- receives from a channel:
v = <-c; // receive value from c, assign to v
<-c; // receive value, throw it away
i := <-c; // receive value, initialize i

Semantics

By default, communication is synchronous.

This means:
1) A send operation on a channel blocks until a receiver is available for the same channel.
2) A receive operation for a channel blocks until a sender is available for the same channel.
Communication is therefore a form of synchronization: two goroutines exchanging data through a channel synchronize at the moment of communication.

Let’s pump some data

func pump(ch chan int) {
for i := 0; ; i++ { ch <- i }
}
ch1 := make(chan int);
go pump(ch1); // pump hangs; we run
fmt.Println(<-ch1); // prints 0
Now we start a looping receiver.
func suck(ch chan int) {
for { fmt.Println(<-ch) }
}
go suck(ch1); // tons of numbers appear
You can still sneak in and grab a value:
fmt.Println(<-ch); // Prints 314159

Functions returning channels

In the previous example, pump was like a generator spewing out values. But there was a lot of fuss allocating channels etc.
Let’s package it up into a function returning the channel of values.

func pump() chan int {
ch := make(chan int);
go func() {
for i := 0; ; i++ { ch <- i }
}();
return ch
}
stream := pump();
fmt.Println(<-stream); // prints 0
This is a very important idiom.

Channel functions everywhere

I am avoiding repeating famous examples you can find elsewhere. Here are a couple to look up:
1) The prime sieve; in the language specification and also in the tutorial.
2) Doug McIlroy’s power series work.

Range and channels

The range clause on for loops accepts a channel as an operand, in which case the for loops over the values received from the channel.

We rewrote pump; here’s the rewrite for suck, making it launch the goroutine as well:

func suck(ch chan int) {
go func() {
for v := range ch { fmt.Println(v) }
}()
}
suck(pump()); // doesn’t block now

Closing a channel

What if we want to signal that a channel is done?
We close it with a built-in function:
close(ch)
and test that state using closed():

if closed(ch) { fmt.Println(”done”) }

Once a channel is closed and every sent value has been received, every subsequent receive operation will recover a zero value.
In practice, you rarely need close except in certain idiomatic situations.

When a channel closes

There are subtleties about races, so closed() succeeds only after you receive one zero value on the channel. The obvious loop isn’t right. To use closed() correctly you need to say:

for {
v := <-ch;
if closed(ch) { break }
fmt.Println(v)
}

But of course, the range clause does that for you.
for v := range ch {
fmt.Println(v)
}

Iterators

Now we have all the pieces to write an iterator for a container. Here is the code for Vector:

// Iterate over all elements
func (p *Vector) iterate(c chan Element) {
for i, v := range p.a { // p.a is a slice
c <- v
}
close(c); // signal no more values
}
// Channel iterator.
func (p *Vector) Iter() chan Element {
c := make(chan Element);
go p.iterate(c);
return c;
}

Using the iterator

Now that Vector has an iterator, we can use it:

vec := new(vector.Vector);
for i := 0; i < 100; i++ {
vec.Push(i*i)
}
i := 0;
for x := range vec.Iter() {
fmt.Printf("vec[%d] is %d\n", i, x.(int));
i++;
}

Channel directionality

In its simplest form a channel variable is an unbuffered (synchronous) value that can be used to send and receive.

A channel type may be annotated to specify that it may only send or only receive:

var recv_only <-chan int;
var send_only chan<- int;

Channel directionality (II)

All channels are created bidirectional, but we can assign them to directional channel variables.
Useful for instance in functions, for (type) safety:
func sink(ch <-chan int) {
for { <-ch }
}
func source(ch chan<- int) {
for { ch <- 1 }
}
var c = make(chan int); // bidirectional
go source(c);
go sink(c);

Synchronous channels

Synchronous channels are unbuffered. Sends do not complete until a receiver has accepted the value.

c := make(chan int);
go func() {
time.Sleep(60*1e9);
x := <-c;
fmt.Println("received", x);
}();
fmt.Println("sending", 10);
c <- 10;
fmt.Println("sent", 10);
Output: sending 10 (happens immediately)
sent 10 (60s later, these 2 lines appear)
received 10

Asynchronous channels

A buffered, asynchronous channel can be created by giving make an argument, the number of elements in the buffer.
c := make(chan int, 50);
go func() {
time.Sleep(60*1e9);
x := <-c;
fmt.Println("received", x);
}();
fmt.Println("sending", 10);
c <- 10;
fmt.Println("sent", 10);
Output: sending 10 (happens immediately)
sent 10 (now)
received 10 (60s later)

Buffer is not part of the type

Note that the buffer's size, or even its existence, is not part of the channel's type, only of the value. This code is therefore legal, although dangerous:
buf = make(chan int, 1);
unbuf = make(chan int);
buf = unbuf;
unbuf = buf;

Buffering is a property of the value, not of the type.

Testing for communicability

Can a receive proceed without blocking? Need to find out and execute, or not, atomically.
"Comma ok" to the rescue:
v, ok = <-c; // ok=true if v received value

Can a send proceed without blocking? Need to find out and execute, or not, atomically. Use send as a boolean expression:
ok := c <- v;
or
if c <- v { fmt.Println("sent value") }

Select

Select is a control structure in Go analogous to a communications switch statement. Each case must be a communication, either send or receive.

var c1, c2 chan int;
select {
case v := <-c1:
fmt.Printf("received %d from c1\n", v)
case v := <-c2:
fmt.Printf("received %d from c2\n", v)
}

Select executes one runnable case at random. If no case is runnable, it blocks until one is. A default clause is always runnable

Related posts:

  1. Concurrency
  2. Multiplexing
  3. Select

Leave Your Response

* Name, Email, Comment are Required