In the previous example we saw how to manage simple counter state using atomic operations. For more complex state we can use a mutex to safely access data across multiple goroutines.
Container holds a map of counters; since we want to update it concurrently from multiple goroutines, we add a Mutex
to synchronize access. Note that mutexes must not be copied, so if this struct
is passed around, it should be done by pointer.
Lock the mutex before accessing counters
; unlock it at the end of the function using a defer statement.
[maxwell@oracle-db-19c Day05]$ vim mutexes.go
[maxwell@oracle-db-19c Day05]$ cat mutexes.go
package main
import (
"fmt"
"sync"
)
type Container struct{
mu sync.Mutex
counters map[string]int
}
func (c *Container) inc(name string){
c.mu.Lock()
defer c.mu.Unlock()
c.counters[name]++
}
func main(){
c := Container{
counters: map[string]int{"a":0, "b":0},
}
var wg sync.WaitGroup
doIncrement := func(name string, n int){
for i:= 0; i < n; i++ {
c.inc(name)
}
wg.Done()
}
wg.Add(3)
go doIncrement("a", 10000)
go doIncrement("a", 10000)
go doIncrement("b", 10000)
wg.Wait()
fmt.Println(c.counters)
}
[maxwell@oracle-db-19c Day05]$ go run mutexes.go
map[a:20000 b:10000]
[maxwell@oracle-db-19c Day05]$
In the previous example we used explicit locking with mutexes to synchronize access to shared state across multiple goroutines. Another option is to use the built-in synchronization features of goroutines and channels to achieve the same result. This channel-based approach aligns with Go’s ideas of sharing memory by communicating and having each piece of data owned by exactly 1 goroutine.
[maxwell@oracle-db-19c Day05]$ vim stateful_goroutines.go
[maxwell@oracle-db-19c Day05]$ cat stateful_goroutines.go
package main
import (
"fmt"
"math/rand"
"sync/atomic"
"time"
)
type readOp struct {
key int
resp chan int
}
type writeOp struct {
key int
val int
resp chan bool
}
func main() {
var readOps uint64
var writeOps uint64
reads := make(chan readOp)
writes := make(chan writeOp)
go func(){
var state = make(map[int]int)
for {
select {
case read := <-reads:
read.resp <- state[read.key]
case write := <-writes:
state[write.key] = write.val
write.resp <- true
}
}
}()
for r := 0; r < 100; r++ {
go func(){
for{
read := readOp{
key: rand.Intn(5),
resp: make(chan int)}
reads <- read
<-read.resp
atomic.AddUint64(&readOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
for w := 0; w < 10; w++ {
go func(){
for {
write := writeOp{
key: rand.Intn(5),
val: rand.Intn(100),
resp: make(chan bool)}
writes <- write
<-write.resp
atomic.AddUint64(&writeOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
time.Sleep(time.Second)
readOpsFinal := atomic.LoadUint64(&readOps)
fmt.Println("readOps:", readOpsFinal)
writeOpsFinal := atomic.LoadUint64(&writeOps)
fmt.Println("writeOps", writeOpsFinal)
}
[maxwell@oracle-db-19c Day05]$ go run stateful_goroutines.go
readOps: 65503
writeOps 6550
[maxwell@oracle-db-19c Day05]$
Go’s sort package implements sorting for builtins and user-defined types. We’ll look at sorting for builtins first.
Sort methods are specific to the builtin type; here’s an example for strings. Note that sorting is in-place, so it changes the given slice and doesn’t return a new one.
We can also use sort
to check if a slice is already in sorted order.
[maxwell@oracle-db-19c Day05]$ vim sorting.go
[maxwell@oracle-db-19c Day05]$ cat sorting.go
package main
import (
"fmt"
"sort"
)
func main(){
strs := []string{"c","a", "b"}
sort.Strings(strs)
fmt.Println("Strings: ", strs)
ints := []int{7, 2, 4}
sort.Ints(ints)
fmt.Println("Ints: ", ints)
s := sort.IntsAreSorted(ints)
fmt.Println("Sorted: ", s)
}
[maxwell@oracle-db-19c Day05]$ go run sorting.go
Strings: [a b c]
Ints: [2 4 7]
Sorted: true
[maxwell@oracle-db-19c Day05]$
Sometimes we’ll want to sort a collection by something other than its natural order. For example, suppose we wanted to sort strings by their length instead of alphabetically. Here’s an example of custom sorts in Go.
In order to sort by a custom function in Go, we need a corresponding type. Here we’ve created a byLength
type that is just an alias for the builtin []string
type.
We implement sort.Interface
- Len
, Less
, and Swap
- on our type so we can use the sort
package’s generic Sort
function. Len
and Swap
will usually be similar across types and Less
will hold the actual custom sorting logic. In our case we want to sort in order of increasing string length, so we use len(s[i])
and len(s[j])
here.
With all of this in place, we can now implement our custom sort by converting the original fruits
slice to byLength
, and then use sort.Sort
on that typed slice.
[maxwell@oracle-db-19c Day05]$ vim sorting_by_functions.go
[maxwell@oracle-db-19c Day05]$ cat sorting_by_functions.go
package main
import (
"fmt"
"sort"
)
type byLength []string
func (s byLength) Len() int {
return len(s)
}
func (s byLength) Swap(i, j int){
s[i],s[j] = s[j],s[i]
}
func (s byLength) Less(i, j int) bool {
return len(s[i]) < len(s[j])
}
func main(){
fruits := []string{"peach","banana","kiwi"}
sort.Sort(byLength(fruits))
fmt.Println(fruits)
}
[maxwell@oracle-db-19c Day05]$ go run sorting_by_functions.go
[kiwi peach banana]
[maxwell@oracle-db-19c Day05]$
A panic
typically means something went unexpectedly wrong. Mostly we use it to fail fast on errors that shouldn’t occur during normal operation, or that we aren’t prepared to handle gracefully.
We’ll use panic throughout this site to check for unexpected errors. This is the only program on the site designed to panic.
A common use of panic is to abort if a function returns an error value that we don’t know how to (or want to) handle. Here’s an example of panic
king if we get an unexpected error when creating a new file.
Running this program will cause it to panic, print an error message and goroutine traces, and exit with a non-zero status.
[maxwell@oracle-db-19c Day05]$ vim panic.go
[maxwell@oracle-db-19c Day05]$ cat panic.go
package main
import "os"
func main(){
panic("a problem")
_,err := os.Create("/tmp/file")
if err != nil {
panic(err)
}
}
[maxwell@oracle-db-19c Day05]$ go run panic.go
panic: a problem
goroutine 1 [running]:
main.main()
/home/maxwell/projects/golang_projects/go_by_examples/Day05/panic.go:6 +0x27
exit status 2
[maxwell@oracle-db-19c Day05]$