Featured image by Egon Elbre
- Continuing to work through https://golangbot.com/learn-golang-series/
- Tutorials 23 to 33.
- Continuing to watch some of Golang University 101
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 useNew
.
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 anerror
. - 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)
}