CONCURRENCY
IN GO
1
GOROUTINES FUNDAMENTAL UNIT
01 02 03
Function running Not OS threads or Concurrent coroutines
concurrently green threads which are not pre-
alongside other code (threads managed by emptive i.e. they can’t
a language’s runtime) be interrupted
2
M:N scheduler
M Mapped To N
Green OS
Threads Threads
3
FORK JOIN MODEL
sayHello := func() {
fmt.Println(“Hello from Goroutine”);
}
/* No Join point */
go sayHello()
4
ISSUES IN PREVIOUS CODE
**NO JOIN POINT
Fixes available :
time.Sleep(1 * time.Millisecond)) It
creates a race condition
Introduce a Join point It
guarantees program’s correctness and remove the
race condition
CREATING A JOIN POINT
var wg sync.WaitGroup
sayHello := func() {
defer wg.Done()
fmt.Println(“Hello from Goroutine”);
}
wg.Add(1)
go sayHello()
wg.Wait() This is the Join point
6
MEMORY USAGE BY A GOROUTINE
~ 2-3 Kb memory allocated to goroutine when
created
Go runtime grows or shrinks memory for
storage automatically
Goroutines share same address space
GC does NOTHING to collect abandoned
goroutines. Blocked goroutines hang around
until process exits.
7
MEMORY USAGE BY GOROUTINE
package main
import (
"fmt"
"runtime"
"sync"
)
/* Program to understand memory allocated to newly created goroutines */
func main() {
memConsumed := func() uint64 {
runtime.GC()
var s runtime.MemStats
runtime.ReadMemStats(&s)
return s.Sys
}
8
MEMORY USAGE BY GOROUTINE
var c <-chan interface{}
var wg sync.WaitGroup
noop := func() {
wg.Done()
// Blocking read operation on empty channel keeping the goroutine alive
<-c
}
const numGoroutines = 1e4
wg.Add(numGoroutines)
before := memConsumed()
for i := numGoroutines; i > 0; i-- {
// Kicking in new goroutine
go noop()
}
wg.Wait()
after := memConsumed()
fmt.Printf("%.3fkb", float64(after-before)/numGoroutines/1000)
9
}
waitGroup from sync package
hello := func(wg *sync.WaitGroup, id int) {
defer wg.Done()
fmt.Printf("Hello from %v!\n", id)
}
const numGreeters = 3
var wg sync.WaitGroup
wg.Add(numGreeters)
go hello(&wg, 1)
go hello(&wg, 2)
go hello(&wg, 3)
wg.Wait()
10
Mutex & RWMutex from sync package
CONCURRENCY CONSTRUCT FOR SHARING ACCESS VIA SYNCHRONISATION
Goroutine 1 Goroutine 2
Lock()
MUTEX
Lock acquired
Lock()
Failed to acquire Lock
Unlock()
Lock released
MUTEX
Lock()
Lock acquired
11
Mutex & RWMutex from sync package
var count int for i := 0; i <= 5; i-- { // Increment
var lock sync.Mutex arithmetic.Add(1)
increment := func(){ go func() {
lock.Lock() defer arithmetic.Done()
defer lock.Unlock() increment()
count++ }()
fmt.Printf(”+ing: %d\n", count) }
} for i := 0; i <= 5; i-- { // Decrement
decrement := func() { arithmetic.Add(1)
lock.Lock() go func() {
defer lock.Unlock() defer arithmetic.Done()
count–- decrement()
fmt.Printf(”-ing: %d\n", count) }()
} }
var arithmetic sync.WaitGroup arithmetic.Wait()
fmt.Println("Arithmetic complete.") 12
Mutex & RWMutex from sync package
Goroutine 1 Goroutine 2
RLock()
RWMUTEX
Lock acquired
RLock()
Lock() Lock acquired
Failed to acquire lock
RUnlock()
RUnlock() Read Lock released
RWMUTEX
Read Lock released
Lock()
Full Lock acquired 13
c := sync.NewCond(&sync.Mutex{})
Cond queue := make([]interface{}, 0, 10)
removeFromQueue := func(delay time.Duration) {
time.Sleep(delay)
c.L.Lock()
queue = queue[1:]
fmt.Println("Removed from queue")
c.L.Unlock()
for conditionTrue() == false { c.Signal()
}
}
A condition waiting to be met for i := 0; i < 10; i++ {
to break the loop and let c.L.Lock()
goroutine continue execution for len(queue) == 2 {
c.Wait()
}
fmt.Println("Adding to queue")
queue = append(queue, struct{}{})
go removeFromQueue(1 * time.Second)
c.L.Unlock() 14