==
// Example-1: sync.RWMutex.all-writers-with-random-sleep.go
// - Demonstrates WR store-lock behaviour of mutex.RWMutex.
// - WR store-lock is mutually exclusive for any other lock of type mutex.RWMutex.
// - Therefore, the resources - data and code in the critical section - are exclusively
// available for the go-routine that acquires WR store-lock.
package main
import (
"fmt"
"runtime"
"sync"
"time"
"math/rand"
)
var cacheLock sync.RWMutex
func randomInt(min int, max int) int {
if min >= max {
return -1
}
return rand.Intn(max - min + 1) + min
}
func printMsg(goRoutineName, timeStamp, msg string) {
fmt.Printf("%s:: %s(): %s\n", timeStamp, goRoutineName, msg)
}
func f1(pWG *sync.WaitGroup) {
t := time.Now()
fmt.Printf("%s:: f1(): waiting on wrlock...\n", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond()))
time.Sleep(time.Duration(randomInt(1000, 3000)) * time.Millisecond)
cacheLock.Lock()
t = time.Now()
printMsg("f1", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond()),
"got write-store-lock. am doing something great.")
time.Sleep(time.Duration(randomInt(1000, 3000)) * time.Millisecond)
cacheLock.Unlock()
t = time.Now()
fmt.Printf("%s:: f1(): wrlock released.\n", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond()))
pWG.Done()
}
func f2(pWG *sync.WaitGroup) {
t := time.Now()
fmt.Printf("%s:: f2(): waiting on wrlock...\n", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond()))
time.Sleep(time.Duration(randomInt(1000, 3000)) * time.Millisecond)
cacheLock.Lock()
t = time.Now()
printMsg("f2", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond()),
"i've got write-store-lock.")
time.Sleep(time.Duration(randomInt(1000, 3000)) * time.Millisecond)
cacheLock.Unlock()
t = time.Now()
fmt.Printf("%s:: f2(): wrlock released.\n", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond()))
pWG.Done()
}
// Example-2: sync.RWMutex.all-readers-with-random-sleep.go
// main() go-routine creates a waitgroup of 2 other go-routines. cacheLock is of type mutex.RWMutex.
// Each go-routine is trying to acquire write lock over cacheLock.
// Whichever go-routine, main or f1() or f2(), acquires cacheLock first gets to execute its critical
// section first. This's how the contention amongst these 3 go-routines is resolved.
// simple fmt.Printf() calls help to understand the sequence of lock and unlock.
func main() {
rand.Seed(time.Now().UnixNano()) // seeding to nano second granularity.
runtime.GOMAXPROCS(runtime.NumCPU()) // allocates one logical processor for the scheduler to use
var wg sync.WaitGroup
wg.Add(2)
// go-routines f1() and f2() are made part of workgroup wg.
go f1(&wg)
go f2(&wg)
t := time.Now()
fmt.Printf("%s:: main(): waiting on wrlock...\n", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond()))
time.Sleep(time.Duration(randomInt(1000, 3000)) * time.Millisecond)
cacheLock.Lock()
t = time.Now()
printMsg("main", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond()),
"it's my turn. i've got write-store-lock.")
time.Sleep(time.Duration(randomInt(1000, 3000)) * time.Millisecond)
cacheLock.Unlock()
t = time.Now()
fmt.Printf("%s:: main(): wrlock released.\n", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond()))
wg.Wait()
t = time.Now()
fmt.Printf("%s:: main(): exiting.\n", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond()))
}
This 2nd code demonstrates how multiple readers are possible at the same instance in time:
==
// - Demonstrates RD store-lock behaviour of mutex.RWMutex.
// - RD store-lock is mutually inclusive for other RD store-lock of type mutex.RWMutex
// and mutually exclusive for WR store-lock of type mutex.RWMutex.
// - Therefore, the resources - data and code in the critical section - are inclusively
// available for the go-routine that acrquires RD store-lock.
package main
import (
"fmt"
"runtime"
"sync"
"time"
"math/rand"
)
var cacheLock sync.RWMutex
func randomInt(min int, max int) int {
if min >= max {
return -1
}
return rand.Intn(max - min + 1) + min
}
func printMsg(goRoutineName, timeStamp, msg string) {
fmt.Printf("%s:: %s(): %s\n", timeStamp, goRoutineName, msg)
}
func f1(pWG *sync.WaitGroup) {
t := time.Now()
fmt.Printf("%s:: f1(): waiting for rdlock...\n", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond()))
time.Sleep(time.Duration(randomInt(1000, 3000)) * time.Millisecond)
cacheLock.RLock()
t = time.Now()
printMsg("f1", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond()),
"got read-store-lock. am doing something great.")
time.Sleep(time.Duration(randomInt(1000, 3000)) * time.Millisecond)
cacheLock.RUnlock()
t = time.Now()
fmt.Printf("%s:: f1(): am done. releasing rdlock.\n", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond()))
pWG.Done()
}
func f2(pWG *sync.WaitGroup) {
t := time.Now()
fmt.Printf("%s:: f2(): waiting for rdlock...\n", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond()))
time.Sleep(time.Duration(randomInt(1000, 3000)) * time.Millisecond)
cacheLock.RLock()
t = time.Now()
printMsg("f2", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond()),
"i've got read-store-lock.")
time.Sleep(time.Duration(randomInt(1000, 3000)) * time.Millisecond)
cacheLock.RUnlock()
t = time.Now()
fmt.Printf("%s:: f2(): rdlock released.\n", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond()))
pWG.Done()
}
// main() go-routine creates a waitgroup of 2 other go-routines. cacheLock is of type mutex.RWMutex.
// Each go-routine is trying to acquire read-store lock over cacheLock.
// Whichever go-routine, main or f1() or f2(), acquires cacheLock first gets to execute its critical
// section. But the difference from the previous code is multiple readers are not possible.
// This's how the contention amongst these 3 go-routines is resolved.
// simple fmt.Printf() calls help to understand the sequence of lock and unlock.
func main() {
rand.Seed(time.Now().UnixNano()) // seeding to nano second granularity.
runtime.GOMAXPROCS(runtime.NumCPU()) // allocates one logical processor for the scheduler to use
var wg sync.WaitGroup
wg.Add(2)
go f1(&wg)
go f2(&wg)
t := time.Now()
fmt.Printf("%s:: main(): waiting for rdlock...\n", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond()))
time.Sleep(time.Duration(randomInt(1000, 3000)) * time.Millisecond)
cacheLock.RLock()
t = time.Now()
printMsg("main", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond()),
"it's my turn. i've got read-store-lock.")
time.Sleep(time.Duration(randomInt(1000, 3000)) * time.Millisecond)
cacheLock.RUnlock()
t = time.Now()
fmt.Printf("%s:: main(): rdlock released.\n", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond()))
wg.Wait()
t = time.Now()
fmt.Printf("%s:: main(): exiting.\n", fmt.Sprintf("%02d-%02d-%d:%02d-%02d-%02d-%06d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond()))
}