Learning Go – Day 4

Featured image by Egon Elbre

Buffered channels

  • A buffered channel will only block when the buffer is full. Meaning “producers” will produce values that are stored in a buffer for consumers to consume and the producer will only block once the buffer is full. Also consumers will only block when the buffer is empty.
  • Create a buffered channel.
channel := make(chan type, capacity)

names := make(chan string, 100)
  • Example of a producer and consumer.
package main

import (
	"fmt"
)

func write(ch chan int, capacity int) {
	for i := 0; i < capacity*capacity; i++ {
		ch <- i
		fmt.Println("successfully wrote", i, "to ch")
	}
	close(ch)
}

func main() {
	const capacity = 50
	ch := make(chan int, capacity)
	go write(ch, capacity)
	for v := range ch {
		fmt.Println("read value", v, "from ch")
	}
}
  • When you close a buffered channel, then receivers can still read values from the channel and once the buffer is empty the zero value of the type will be returned.
value, stillOpen := <-channel
if stillOpen == false {
// channel was closed and no more values to consume
}
  • Use cap(channel) to get the capacity of a channel.
  • Use len(channel) to get the number of values queued in the channel.

WaitGroup

  • Used to wait on a collection of goroutines to finish.
  • Import “sync”.
  • Increment the number of goroutines waiting for by using waitgroupVar.Add(number).
  • Signal that a goroutine is finished by using waitgroupVar.Done().
  • Wait until all goroutines are finished by using waitgroupVar.Wait().
  • See this example.
import "sync"

func doSomething(wg *sync.WaitGroup) { //<-- Note we are passing a pointer!
...
	wg.Done() // signal this worker is done
}

var wg sync.WaitGroup
wg.Add(2) // indicate we will wait for 2 workers
go doSomething(&wg)
go doSomething(&wg)

wg.Wait() // wait until all workers have .Done()
//NOTE: You pass the wait group by reference (i.e. pointer) or each
// goroutine will get a copy of its own WaitGroup!

Select

  • Allows you to block and wait for any number of channel send or receive operations. When multiple channels are ready, then a random case will be chosen to process.
ch1 := make(chan string)
ch2 := make(chan string)
go processSomething(ch1)
go processSomething(ch2)

select {
case val1 := <- ch1:
	// received from channel 1
case val2 := <- ch2:
	// received from channel 2
}
  • You can also add a default case that will be called when no other case is ready and thus the select can be non-blocking.

Mutex

  • Critical section recap. A critical section is a section of code that must be guaranteed to only be executing on a single thread (e.g. incrementing a counter) at a time and thus prevent a race condition.
  • Use a mutex to protect a critical section and thus only one goroutine can execute the critical section code.
import "sync"

var mutex sync.Mutex
mutex.Lock()
... // critical section
mutex.Unlock()

New

  • Go is not OOP in the “traditional” sense. You don’t have classes and you don’t have constructors.
  • How do you prevent an instance of a struct to be made where the zero value is not valid. Meaning this is where constructors in other languages come in.
  • You un-export (e.g. make “private”) the type by using camelCase (lowercase initial letter).
  • You declare an exported function named NewT where T is the name of the type you are constructing and returning. Seems to be convention that if only one type is exported from the package then you simply use New.
package person

type person struct { //<- note the lowercase initial
...
}

func New(params) person {
... // construct and return a valid person
}

// ... some other package
p1 := person.New()

Defer

  • Use defer to execute a function/method call just before the containing function exits.
  • Arguments passed to the deferred function will be the value at the time the defer call is “scheduled” and not when the deferred function is actually being executed.
a := 42
defer callMeAtTheEnd(a)
a = -200
// here the containing function is finished and thus
// code execution will look something like this
// callMeAtTheEnd(42)
  • Multiple defer call can be “scheduled” and a stack is used where the deferred calls are made in LIFO (Last In First Out) order.
defer one()
defer two()
defer three()
// The order of calls will be
// three(), two(), one()

Error handling

  • Errors are just values. They implement the error interface.
  • Go doesn’t have “raise exception” or “try catch” handling.
  • When an error is returned from a function, then it is convention to have it as the last type.
func Open(name string) (file *File, err error)

f, err := Open("/something")
if err != nil {
...
}
  • Idiomatic Go is to check the error against nil.
  • The error interface looks like this:
type error interface {  
    Error() string
}
  • Which means that any type that implements Error() string method can be used as an error.
  • Easy to way to create “simple” errors.
import "errors"

err := errors.New("My shiny new error that is just a string")

// What if I want a formatted string error?
import "fmt"

err := fmt.Errorf("Ouch the server returned %d status code", status)

First class functions

  • Recap: A first class type means that it can be assigned to variables, passed to functions as arguments and be returned from functions.
  • In Go functions are first class (meaning can be assigned to a variable, passed to functions and returned from functions).
  • Anonymous function.
callMe := func() {
... // do something
}

...
callMe()

// Or to declare and call without assigning to a variable
func() {
...
}() // <- note the () to execute the function

// Yes you can have arguments
func(name string) {
...
}("Jannie")
  • How do you do “typealiasing” like I would in Swift to give a closure a type?
type Handler func(who string, what string) int

var handler Handler = func(who string, what string) int {
...
}

result := handler("jannie", "did something")
  • Higher-order function: A function that takes one or more functions as arguments and that returns a function.
  • Closures: Functions that capture values from the outside scope.
a := 42
func() {
	fmt.Println("I am a closure that closes over a = ", a)
}