Channels are a typed conduit through which you can send and receive values with the channel operator, <-.

ch <- v    // Send v to channel ch.
v := <-ch  // Receive from ch, and assign value to v.

The data flows in the direction of the arrow.

Channels must be created before use (like maps and slices):

ch := make(chan int)

Example:

package main
 
import "fmt"
 
func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // send sum to c
}
 
func main() {
	s := []int{7, 2, 8, -9, 4, 0}
 
	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // receive from c
 
	fmt.Println(x, y, x+y) // "-5 17 12"
}

Buffered channels

Channels can be buffered.

ch := make(chan int, 100)

Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty.

q Why they are needed?

Close

A sender can close a channel to indicate that no more values will be sent. Receivers can test whether a channel has been closed with a second parameter:

v, ok := <-ch

ok is false if there are no more values to receive and the channel is closed.

Only the sender should close a channel, never the receiver. Sending on a closed channel will cause a panic.

Channels aren’t like files; you don’t usually need to close them. Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop.

Range

The loop for i := range c receives values from the channel repeatedly until it is closed.

func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
	}
	close(c)
}
 
func main() {
	c := make(chan int, 100)
	go fibonacci(cap(c), c)
	for i := range c {
		fmt.Println(i)
	}
}

Output is:

0
1
1
2
3
5
8
13
21
34

Select

The select statement lets a goroutine wait on multiple communication operations.

A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

q Didn’t understood that.

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}
 
func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}

Output:

1
1
2
3
5
8
13
21
34
quit

Default selection

The default case in a select is run if no other case is ready.

select {
case i := <-c:
    // use i
default:
    // receiving from c would block
}

My solution to the https://go.dev/tour/concurrency/8 task:

package main
 
import (
	"fmt"
	"golang.org/x/tour/tree"
)
 
// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
	if t.Left != nil {
		Walk(t.Left, ch)
	}
	ch <- t.Value
	if t.Right != nil {
		Walk(t.Right, ch)
	}
}
 
// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
	ch1 := make(chan int)
	ch2 := make(chan int)
	
	go Walk(t1, ch1)
	go Walk(t2, ch2)
	
	for range 10 {
		v1, v2 := <-ch1, <-ch2
		if v1 != v2 {
			return false
		}
	}
	
	return true
}
 
func main() {
	ch := make(chan int)
	go Walk(tree.New(1), ch)
	for range 10 {
		fmt.Println(<-ch)
	}
	isSame := Same(tree.New(1), tree.New(2))
	fmt.Println(isSame)
}

Output:

1
2
3
4
5
6
7
8
9
10
false

Mutual exclusion

Go’s standard library provides mutual exclusion with sync.Mutex and its two methods Lock and Unlock.

Example:

package main
 
import (
	"fmt"
	"sync"
	"time"
)
 
// SafeCounter is safe to use concurrently.
type SafeCounter struct {
	mu sync.Mutex
	v  map[string]int
}
 
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
	c.mu.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	c.v[key]++
	c.mu.Unlock()
}
 
// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
	c.mu.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	defer c.mu.Unlock()
	return c.v[key]
}
 
func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}
 
	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
}

We can define a block of code to be executed in mutual exclusion by surrounding it with a call to Lock and Unlock as shown on the Inc method. We can also use defer to ensure the mutex will be unlocked as in the Value method.

References

References