Skip to content

Commit babfc90

Browse files
committed
finish with mutex exercises
1 parent 966eea8 commit babfc90

File tree

11 files changed

+236
-46
lines changed

11 files changed

+236
-46
lines changed

mutexes/exercises/1/solution/solution.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ import (
1616

1717
// run the tests using:
1818
// GOFLAGS="-count=1" go test -race .
19+
20+
// SOLUTION
21+
// The problem with this code is it has both:
22+
// a deadlock and a race condition.
23+
// The deadlock happens because we accidentally
24+
// forget to call Unlock().
25+
// The race condition happens because
26+
// we mix and match Atomics with Mutexes
1927
func main() {
2028
clicks := exercise()
2129
fmt.Println("total clicks:", clicks.total)

mutexes/exercises/2/exercise.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func exercise(c *catalog, ids ...string) {
4242
c.get(id)
4343
}
4444
c.add("generated_"+strconv.Itoa(i), "generated product")
45-
}(i+1)
45+
}(i + 1)
4646
}
4747

4848
wg.Wait()
@@ -57,15 +57,15 @@ func (c *catalog) add(id, product string) {
5757
c.mu.Lock()
5858
defer c.mu.Unlock()
5959
// simulate load
60-
time.Sleep(500*time.Nanosecond)
60+
time.Sleep(500 * time.Nanosecond)
6161
c.data[id] = product
6262
}
6363

6464
func (c *catalog) get(id string) string {
6565
c.mu.Lock()
6666
defer c.mu.Unlock()
6767
// simulate load
68-
time.Sleep(500*time.Nanosecond)
68+
time.Sleep(500 * time.Nanosecond)
6969
// avoid key existence check
7070
return c.data[id]
7171
}

mutexes/exercises/2/solution/solution.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ import (
1717

1818
// run the tests using:
1919
// GOFLAGS="-count=1" go test .
20+
21+
// SOLUTION
22+
// The problem with this code is mainly because,
23+
// we are using a regular Mutex, thus slowing down
24+
// all the read operations. Using a RWMutex instead
25+
// will give the same results at an improved speed
2026
func main() {
2127
c := &catalog{data: map[string]string{
2228
"p1": "apples",
@@ -42,7 +48,7 @@ func exercise(c *catalog, ids ...string) {
4248
c.get(id)
4349
}
4450
c.add("generated_"+strconv.Itoa(i), "generated product")
45-
}(i+1)
51+
}(i + 1)
4652
}
4753

4854
wg.Wait()
@@ -57,15 +63,15 @@ func (c *catalog) add(id, product string) {
5763
c.mu.Lock()
5864
defer c.mu.Unlock()
5965
// simulate load
60-
time.Sleep(500*time.Nanosecond)
66+
time.Sleep(500 * time.Nanosecond)
6167
c.data[id] = product
6268
}
6369

6470
func (c *catalog) get(id string) string {
6571
c.mu.RLock()
6672
defer c.mu.RUnlock()
6773
// simulate load
68-
time.Sleep(500*time.Nanosecond)
74+
time.Sleep(500 * time.Nanosecond)
6975
// avoid key existence check
7076
return c.data[id]
7177
}

mutexes/exercises/3/solution/solution.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ import (
1717
// run the tests using:
1818
// GOFLAGS="-count=1" go test .
1919

20+
// SOLUTION
21+
// The problem with this implementation is the fine-grained
22+
// defined context by the mutex, resulting in an undesired result.
23+
// All we really need to fix the problem is limit the context
24+
// of the mutex, which will also give us the expected result
2025
func main() {
2126
now := time.Now()
2227
count := exercise()
@@ -37,7 +42,7 @@ func exercise() int {
3742
mu.Lock()
3843
a = A
3944
b = B
40-
count += a+b
45+
count += a + b
4146
A++
4247
B++
4348
mu.Unlock()

mutexes/exercises/4/exercise.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func exercise() int {
5050
defer wg.Done()
5151
muA.Lock()
5252
defer muA.Unlock()
53-
A+=5
53+
A += 5
5454
}()
5555
}
5656

mutexes/exercises/4/solution/solution.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ import (
1717
// run the tests using:
1818
// GOFLAGS="-count=1" go test .
1919

20+
// SOLUTION
21+
// The problem with this code that uses 2 mutexes is the fact
22+
// That the second mutex depends on the use of the first mutex,
23+
// thus creating a perfect environment for lock contention.
24+
// Since operations on A and B are independent, solving this is
25+
// easy, all we have to do is release the mutex on A ASAP,
26+
// thus allowing the other go routine to use it immediately
27+
// and as a result the whole program executes faster
2028
func main() {
2129
now := time.Now()
2230
A := exercise()
@@ -50,7 +58,7 @@ func exercise() int {
5058
defer wg.Done()
5159
muA.Lock()
5260
defer muA.Unlock()
53-
A+=5
61+
A += 5
5462
}()
5563
}
5664

mutexes/exercises/5/exercise.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
// GOFLAGS="-count=1" go test .
2020

2121
func main() {
22+
// p1 and p2 are the number of executions per process
2223
p1, p2 := exercise(time.Second)
2324
fmt.Println("p1:", p1)
2425
fmt.Println("p2:", p2)

mutexes/exercises/5/solution/solution.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ import (
1818
// run the tests using:
1919
// GOFLAGS="-count=1" go test .
2020

21+
// SOLUTION
22+
// The problem we're solving here is Starvation.
23+
// Even if all operations are equal in terms of time burst.
24+
// Because we have 3 calls to read and 1 call to write,
25+
// the mutex local to each call is being used more frequently,
26+
// thus allowing one process to acquire it way more often than the other.
27+
// The fix simply using the mutex evenly distributing it across workers/Gs.
28+
// In other words, just use the mutex directly, not inside the read call.
29+
// Always be careful with mutexes local to the methods and test for starvation.
2130
func main() {
2231
p1, p2 := exercise(time.Second)
2332
fmt.Println("p1:", p1)

mutexes/exercises/6/exercise.go

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
// Find what's wrong in the exercise() function
1111
// Make sure all the tests are passing
1212
// DO NOT remove any Sleep() calls
13+
// DO NOT remove any steps, i.e: transfer, check, revert
1314

1415
// try running this with the -race flag
1516
// go run -race exercise.go
@@ -18,24 +19,34 @@ import (
1819
// GOFLAGS="-count=1" go test .
1920

2021
func main() {
21-
exercise()
22+
now := time.Now()
23+
accounts := exercise()
24+
for _, a := range accounts {
25+
fmt.Printf("'%s' amount: $%v\n", a.id, a.amount)
26+
fmt.Printf("'%s' transactions: %v\n", a.id, a.transactions)
27+
}
28+
fmt.Println("elapsed:", time.Since(now))
2229
}
2330

24-
func exercise() {
31+
func exercise() []*account {
2532
var mu sync.Mutex
26-
var n1, n2 int
27-
f1, f2 := file{mu: &mu, name: "f1"}, file{mu: &mu, name: "f2"}
28-
//write := func(f *file, name1, name2 string) {
29-
// for i := 0; i < 5; i++ {
30-
// if f.write([]byte(name1)) || f.write([]byte(name2)) {
31-
// return
32-
// }
33-
// }
34-
//}
35-
write := func(f *file, n1, n2 *int) {
33+
accounts := []*account{
34+
{id: "a1", amount: 5},
35+
{id: "a2", amount: 10},
36+
}
37+
b1 := bank{mu: &mu, name: "bank1"}
38+
b2 := bank{mu: &mu, name: "bank2"}
39+
write := func(b *bank, amount float64, accounts ...*account) {
3640
for i := 0; i < 5; i++ {
37-
if f.write(n1) || f.write(n2) {
38-
fmt.Println(f.name, "successfully wrote to file")
41+
ok := true
42+
for _, a := range accounts {
43+
if !b.transfer(a, amount) {
44+
ok = false
45+
break
46+
}
47+
}
48+
if ok {
49+
fmt.Printf("'%s' successfully transferred: $%v\n", b.name, amount)
3950
return
4051
}
4152
}
@@ -45,48 +56,55 @@ func exercise() {
4556
wg.Add(2)
4657
go func() {
4758
defer wg.Done()
48-
write(&f1, &n1, &n2)
59+
// transfer $200 from bank1 to all accounts
60+
write(&b1, 200, accounts...)
4961
}()
5062
go func() {
5163
defer wg.Done()
52-
write(&f2, &n1, &n2)
64+
// transfer $100 from bank2 to all accounts
65+
write(&b2, 100, accounts...)
5366
}()
5467

5568
wg.Wait()
56-
fmt.Println(n1)
57-
fmt.Println(n2)
69+
return accounts
5870
}
5971

60-
type file struct {
72+
type account struct {
73+
id string
74+
amount float64
75+
transactions []string
76+
}
77+
78+
type bank struct {
6179
name string
6280
mu *sync.Mutex
63-
//data []byte
6481
}
6582

66-
//func (f *file) write(data []byte) bool {
67-
func (f *file) write(n *int) bool {
68-
fmt.Println(f.name, "trying to write", *n)
69-
f.mu.Lock()
70-
//f.data = data
71-
*n += 1
72-
f.mu.Unlock()
83+
func (b *bank) transfer(acc *account, amount float64) bool {
84+
// transfer the money
85+
fmt.Printf("'%s' trying to transfer: $%v to '%s'\n", b.name, amount, acc.id)
86+
b.mu.Lock()
87+
acc.amount += amount
88+
b.mu.Unlock()
7389
time.Sleep(500 * time.Millisecond)
7490

75-
f.mu.Lock()
76-
//if bytes.Equal(f.data, data) {
77-
if *n == 1 {
78-
f.mu.Unlock()
91+
// check if the money were transferred
92+
b.mu.Lock()
93+
// if current balance equals to previous account balance
94+
if acc.amount == acc.amount-amount {
95+
b.mu.Unlock()
96+
tx := fmt.Sprintf("%s: $%v", b.name, amount)
97+
acc.transactions = append(acc.transactions, tx)
7998
return true
8099
}
81-
f.mu.Unlock()
82-
100+
b.mu.Unlock()
83101

84102
time.Sleep(100 * time.Millisecond)
85103

86-
f.mu.Lock()
87-
//f.data = nil
88-
*n -= 1
89-
f.mu.Unlock()
104+
// revert the transfer
105+
b.mu.Lock()
106+
acc.amount -= amount
107+
b.mu.Unlock()
90108
time.Sleep(500 * time.Millisecond)
91109
return false
92110
}

mutexes/exercises/6/exercise_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,28 @@ package main
22

33
import (
44
"testing"
5+
"time"
56
)
67

78
// GOFLAGS="-count=1" go test .
89
func TestExercise(t *testing.T) {
10+
now := time.Now()
11+
accounts := exercise()
12+
elapsed := time.Since(now)
13+
expectedAccounts := []account{
14+
{id: "a1", amount: 305, transactions: []string{"bank2: $100", "bank1: $200"}},
15+
{id: "a2", amount: 310, transactions: []string{"bank2: $100", "bank1: $200"}},
16+
}
17+
18+
if len(accounts) != 2 {
19+
t.Fatalf("expected number of account to be 2, got %d", len(accounts))
20+
}
21+
for i, a := range accounts {
22+
if a.id != expectedAccounts[i].id || a.amount != expectedAccounts[i].amount || len(accounts[i].transactions) != 2 {
23+
t.Fatalf("expected account to be %v, got %v", expectedAccounts[i], a)
24+
}
25+
}
26+
if elapsed > 3*time.Second {
27+
t.Fatalf("exercise took too long: %v", elapsed)
28+
}
929
}

0 commit comments

Comments
 (0)