Skip to content

Commit 966eea8

Browse files
committed
add more mutexes exercises
1 parent 83e7577 commit 966eea8

File tree

28 files changed

+1093
-32
lines changed

28 files changed

+1093
-32
lines changed

mutexes/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,5 +134,6 @@ the database consistency which otherwise is not possible for a non-serial schedu
134134
- [raceacquire - Go Source Code](https://github.com/golang/go/blob/master/src/runtime/race.go#L515)
135135
- [racecall - Go Source Code](https://github.com/golang/go/blob/master/src/runtime/race.go#L348)
136136
- [racecall - GOASM Source Code](https://github.com/golang/go/blob/master/src/runtime/race_amd64.s#L384)
137+
- [RWMutex - Go Source Code](https://github.com/golang/go/blob/master/src/sync/rwmutex.go#L28)
137138

138139
[Home](https://github.com/golang-basics/concurrency)

mutexes/atomic-mutex-mix/main.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
"sync/atomic"
7+
)
8+
9+
// try running this with the -race flag
10+
// go run -race main.go
11+
func main() {
12+
var count int32
13+
var mu sync.Mutex
14+
var wg sync.WaitGroup
15+
16+
for i := 0; i < 10000; i++ {
17+
wg.Add(2)
18+
go func() {
19+
defer wg.Done()
20+
atomic.AddInt32(&count, 1)
21+
}()
22+
go func() {
23+
defer wg.Done()
24+
mu.Lock()
25+
count++
26+
mu.Unlock()
27+
}()
28+
}
29+
30+
wg.Wait()
31+
// the result is correct, but Go is not able to properly detect a race condition
32+
// due to mixed usage of Mutex and Atomics
33+
fmt.Println("count:", count)
34+
}

mutexes/deadlock-and-race/main.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
)
7+
8+
// Having both deadlock and race
9+
// will result in the program hanging
10+
// when ran with -race flag
11+
// go run -race main.go => will show races, but hang forever
12+
13+
// Try running the program first without -race => fix the deadlock
14+
// go run main.go
15+
// then using the -race flag => fix the race condition
16+
// go run -race main.go
17+
func main() {
18+
var count int
19+
var mu sync.Mutex
20+
var wg sync.WaitGroup
21+
22+
wg.Add(2)
23+
go func() {
24+
defer wg.Done()
25+
count++
26+
27+
}()
28+
go func() {
29+
defer wg.Done()
30+
count++
31+
}()
32+
33+
wg.Wait()
34+
mu.Lock()
35+
mu.Lock()
36+
fmt.Println("count:", count)
37+
}

mutexes/exercises/1/exercise.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
"sync/atomic"
7+
)
8+
9+
// EXERCISE:
10+
// Find what's wrong in the following code
11+
// Make sure all the tests are passing
12+
// Make sure there's no deadlock or race condition
13+
// Hint: Limit the go routines (100), then find and fix the deadlock first
14+
15+
// try running this with the -race flag
16+
// go run -race exercise.go
17+
18+
// run the tests using:
19+
// GOFLAGS="-count=1" go test -race .
20+
func main() {
21+
clicks := exercise()
22+
fmt.Println("total clicks:", clicks.total)
23+
}
24+
25+
func exercise() clickCounter {
26+
var mu sync.Mutex
27+
var wg sync.WaitGroup
28+
clicks := clickCounter{
29+
mu: &mu,
30+
elements: map[string]int{
31+
"btn1": 0,
32+
"btn2": 0,
33+
},
34+
}
35+
36+
for i := 0; i < 1000; i++ {
37+
wg.Add(3)
38+
go func() {
39+
defer wg.Done()
40+
if len(clicks.elements) > 10 {
41+
fmt.Println("we got more than 10 elements")
42+
}
43+
}()
44+
go func() {
45+
defer wg.Done()
46+
clicks.add("btn1")
47+
clicks.add("btn2")
48+
}()
49+
go func() {
50+
defer wg.Done()
51+
total := atomic.LoadInt64(&clicks.total)
52+
if total > 20 {
53+
fmt.Println("something is wrong")
54+
}
55+
}()
56+
}
57+
58+
wg.Wait()
59+
return clicks
60+
}
61+
62+
type clickCounter struct {
63+
mu *sync.Mutex
64+
elements map[string]int
65+
total int64
66+
}
67+
68+
func (c *clickCounter) add(element string) {
69+
c.mu.Lock()
70+
if c.elements[element]+1 > 10 {
71+
return
72+
}
73+
c.elements[element]++
74+
c.total++
75+
c.mu.Unlock()
76+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
)
6+
7+
// GOFLAGS="-count=1" go test -race .
8+
func TestExercise(t *testing.T) {
9+
clicks := exercise()
10+
11+
n1 := clicks.elements["btn1"]
12+
n2 := clicks.elements["btn2"]
13+
total := clicks.total
14+
15+
if n1 != 10 {
16+
t.Fatalf("expected number of clicks for btn1: %d, got: %d", 10, n1)
17+
}
18+
if n2 != 10 {
19+
t.Fatalf("expected number of clicks for btn2: %d, got: %d", 10, n2)
20+
}
21+
if total != 20 {
22+
t.Fatalf("expected total to be: %d, got: %d", 20, total)
23+
}
24+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
)
7+
8+
// EXERCISE:
9+
// Find what's wrong in the following code
10+
// Make sure all the tests are passing
11+
// Make sure there's no deadlock or race condition
12+
// Hint: Limit the go routines (100), then find and fix the deadlock first
13+
14+
// try running this with the -race flag
15+
// go run -race exercise.go
16+
17+
// run the tests using:
18+
// GOFLAGS="-count=1" go test -race .
19+
func main() {
20+
clicks := exercise()
21+
fmt.Println("total clicks:", clicks.total)
22+
}
23+
24+
func exercise() clickCounter {
25+
var mu sync.Mutex
26+
var wg sync.WaitGroup
27+
clicks := clickCounter{
28+
mu: &mu,
29+
elements: map[string]int{
30+
"btn1": 0,
31+
"btn2": 0,
32+
},
33+
}
34+
35+
for i := 0; i < 1000; i++ {
36+
wg.Add(3)
37+
go func() {
38+
defer wg.Done()
39+
clicks.mu.Lock()
40+
defer clicks.mu.Unlock()
41+
if len(clicks.elements) > 10 {
42+
fmt.Println("we got more than 10 elements")
43+
}
44+
}()
45+
go func() {
46+
defer wg.Done()
47+
clicks.add("btn1")
48+
clicks.add("btn2")
49+
}()
50+
go func() {
51+
defer wg.Done()
52+
clicks.mu.Lock()
53+
defer clicks.mu.Unlock()
54+
if clicks.total > 20 {
55+
fmt.Println("something is wrong")
56+
}
57+
}()
58+
}
59+
60+
wg.Wait()
61+
return clicks
62+
}
63+
64+
type clickCounter struct {
65+
mu *sync.Mutex
66+
elements map[string]int
67+
total int64
68+
}
69+
70+
func (c *clickCounter) add(element string) {
71+
c.mu.Lock()
72+
defer c.mu.Unlock()
73+
if c.elements[element]+1 > 10 {
74+
return
75+
}
76+
c.elements[element]++
77+
c.total++
78+
}

mutexes/exercises/2/exercise.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"sync"
7+
"time"
8+
)
9+
10+
// EXERCISE:
11+
// Find what's wrong in the following code
12+
// Make sure all the tests are passing
13+
// DO NOT remove any Sleep() calls
14+
15+
// try running this with the -race flag
16+
// go run -race exercise.go
17+
18+
// run the tests using:
19+
// GOFLAGS="-count=1" go test .
20+
func main() {
21+
c := &catalog{data: map[string]string{
22+
"p1": "apples",
23+
"p2": "oranges",
24+
"p3": "grapes",
25+
"p4": "pineapple",
26+
"p5": "bananas",
27+
}}
28+
29+
now := time.Now()
30+
exercise(c, "p1", "p2", "p3", "p4", "p5")
31+
fmt.Println("elapsed:", time.Since(now))
32+
}
33+
34+
func exercise(c *catalog, ids ...string) {
35+
var wg sync.WaitGroup
36+
wg.Add(1000)
37+
38+
for i := 0; i < 1000; i++ {
39+
go func(i int) {
40+
defer wg.Done()
41+
for _, id := range ids {
42+
c.get(id)
43+
}
44+
c.add("generated_"+strconv.Itoa(i), "generated product")
45+
}(i+1)
46+
}
47+
48+
wg.Wait()
49+
}
50+
51+
type catalog struct {
52+
mu sync.Mutex
53+
data map[string]string
54+
}
55+
56+
func (c *catalog) add(id, product string) {
57+
c.mu.Lock()
58+
defer c.mu.Unlock()
59+
// simulate load
60+
time.Sleep(500*time.Nanosecond)
61+
c.data[id] = product
62+
}
63+
64+
func (c *catalog) get(id string) string {
65+
c.mu.Lock()
66+
defer c.mu.Unlock()
67+
// simulate load
68+
time.Sleep(500*time.Nanosecond)
69+
// avoid key existence check
70+
return c.data[id]
71+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
// GOFLAGS="-count=1" go test .
9+
func TestExercise(t *testing.T) {
10+
c := &catalog{data: map[string]string{
11+
"p1": "apples",
12+
"p2": "oranges",
13+
"p3": "grapes",
14+
"p4": "pineapple",
15+
"p5": "bananas",
16+
}}
17+
18+
now := time.Now()
19+
exercise(c, "p1", "p2", "p3", "p4", "p5")
20+
elapsed := time.Since(now)
21+
numberOfProducts := len(c.data)
22+
23+
if numberOfProducts != 1005 {
24+
t.Fatalf("wrong number of products in the catalog, got: %d", numberOfProducts)
25+
}
26+
if elapsed > 10*time.Millisecond {
27+
t.Fatalf("exercise took too long, elapsed: %v", elapsed)
28+
}
29+
}

0 commit comments

Comments
 (0)