Sage-Code Laboratory
index<--

Go Concurrency

Concurrency is a vast subject. It is defined as a style of programming that enable execution of multiple logical threads in the same time. Go language enable developers to write multi-thread and concurrent lightweight processes.

Page bookmarks:


Start goroutines

A goroutine is a lightweight thread managed by the Go runtime Having a function "f(x,y,z)" we can run the function using "go" keyword asynchronously. After calling goroutine the main program continue to execute, and sometimes can finish faster then it should. Therefore we should wait for all processes to finish before main program ends.


go f(x, y, z)

Goroutines run in the same address space, so the access to shared memory must be synchronized. The "sync" package provides useful primitives, although you won't need them much in Go as there are other primitives: Channels that allow superior implementation.

//file goroutine.go
package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(i, s)

	}
}

func main() {
	go say("run")
    time.Sleep(1000 * time.Millisecond)
    fmt.Println("done");
}

Try it: goroutines.go

Define Channel

Channels are a typed conduit through which you can send and receive values with the channel operator, <-. The data flows in the direction of the arrow. This symbol is sometimes used as a prefix for a channel to access the first value of a channel.

Patterns

// Send v to channel ch.
ch <- v    

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

Create channels

Like maps and slices, channels must be created before use using make this will create a new instance in memory for the channel values:

ch := make(chan int)

By default, sends and receives is blocking the channel until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.

Example:

//file: using_channel.go
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)
}

Note: The example code sums the numbers in a slice, distributing the work between two goroutines. Once both goroutines have completed their computation, it calculates the final result.

Try it: using_channels.go

Closing a channel

A sender can close a channel to indicate that no more values will be sent. Receivers can test whether a channel has been closed by assigning a second parameter to the received expression: after v, ok := <-ch, when "ok" is false, if there are no more values to receive and the channel is closed.

Example:

//file close_channel.go
package main

import (
	"fmt"
)

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, 10)
	go fibonacci(cap(c), c)
	for i := range c {
		fmt.Println(i)
	}
}

Try it: using_channels.go

Notes:

Buffered Channels

Channels can be buffered. Provide the buffer length as the second argument to "make" to initialize a buffered channel:

ch := make(chan int, 100)

Example:

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

//file buffer_channel.go
package main

import "fmt"

func main() {
	ch := make(chan int, 2)
	ch <- 1
	ch <- 2
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}

Try it: using_channels.go

Select channels

The "select" statement lets a goroutine wait on multiple communication operations. A "select" blocks the main process until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

Example:

//file go_select.go
package main

import "fmt"

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)
}

Try it: go_select.go

Default Selection

Basic sends and receives on channels are blocking. However, we can use select with a default clause to implement non-blocking sends, receives, and even non-blocking multi-way selects.

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

Example:

//file select_default.go
package main

import (
	"fmt"
	"time"
)

func main() {
	tick := time.Tick(100 * time.Millisecond)
	boom := time.After(500 * time.Millisecond)
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:
			fmt.Println("    .")
			time.Sleep(50 * time.Millisecond)
		}
	}
}

Try it: select_default.go

Mutex object

We've seen how channels are great for communication among goroutines. What if we don't need communication but just a way to make sure only one goroutine can access a variable at a time?

This concept is used to avoid conflicts. It is called for short mutex, that means mutual exclusion. Go standard library provides mutual exclusion data structure type: sync.Mutex, that implements two methods: {lock, unlock}

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.

Example:

//file mutex.go
package main

import (
	"fmt"
	"sync"
	"time"
)

// SafeCounter is safe to use concurrently.
type SafeCounter struct {
	v   map[string]int
	mux sync.Mutex
}

// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
	c.mux.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	c.v[key]++
	c.mux.Unlock()
}

// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
	c.mux.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	defer c.mux.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"))
}

Try it: mutex_ob.go


Read next: Go Examples