Skip to content

Commit 43e3518

Browse files
committed
Collection of common concurrency pitfalls tests
0 parents  commit 43e3518

File tree

5 files changed

+174
-0
lines changed

5 files changed

+174
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Collection of Go tests that show common concurrency problems:
2+
3+
* [Race Condition](problems/race_test.go)
4+
* [Deadlock](problems/deadlock_test.go)
5+
* [Starvation](problems/starvation_test.go)

problems/deadlock_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package problems
2+
3+
// Deadlock: blocking of concurrent threads that have a mutual dependency that is impossible to be satisfied.
4+
5+
import (
6+
"testing"
7+
)
8+
9+
// block on the wait channel before writing to the done and ctl channels
10+
func deadlock(wait chan bool, done chan bool, ctl chan bool) {
11+
<-wait
12+
done <- true
13+
ctl <- true
14+
}
15+
16+
func Test_Deadlock(t *testing.T) {
17+
c1 := make(chan bool)
18+
c2 := make(chan bool)
19+
ctl := make(chan bool)
20+
21+
// start mutual dependent go routines
22+
go deadlock(c1, c2, ctl)
23+
go deadlock(c2, c1, ctl)
24+
25+
<-ctl
26+
<-ctl
27+
}

problems/race_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package problems
2+
3+
// Race Conditions: concurrent threads changing shared data without sufficient protection mechanisms.
4+
5+
import (
6+
"testing"
7+
)
8+
9+
const concurrency = 1000
10+
11+
// shared memory
12+
var value = 0
13+
14+
// change the value of unprotected shared memory
15+
func increment(done chan bool) {
16+
value++
17+
done <- true
18+
}
19+
20+
func Test_RaceCondition(t *testing.T) {
21+
done := make(chan bool, concurrency)
22+
23+
// start N go routines
24+
for i := 0; i < concurrency; i++ {
25+
go increment(done)
26+
}
27+
28+
// wait for all go routines to finish
29+
for i := 0; i < concurrency; i++ {
30+
<-done
31+
}
32+
33+
// assert that writes were lost due to race conditions
34+
if value != concurrency {
35+
t.Logf("expected value to be: %v but was: %v", concurrency, value)
36+
t.Fatal()
37+
}
38+
}

problems/starvation_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package problems
2+
3+
// Starvation: threads are unable to progress due to greedy or high priority threads blocking shared resources.
4+
5+
import (
6+
"sync"
7+
"testing"
8+
"time"
9+
)
10+
11+
// shared resource
12+
var l = &lock{}
13+
14+
type lock struct {
15+
m sync.Mutex
16+
}
17+
18+
var thr = &threads{}
19+
20+
type threads struct {
21+
created int
22+
finished int
23+
m sync.Mutex
24+
}
25+
26+
func (t *threads) create() {
27+
t.m.Lock()
28+
t.created++
29+
t.m.Unlock()
30+
}
31+
32+
func (t *threads) finish() {
33+
t.m.Lock()
34+
t.finished++
35+
t.m.Unlock()
36+
}
37+
38+
func (t *threads) values() (int, int) {
39+
t.m.Lock()
40+
defer t.m.Unlock()
41+
return t.created, t.finished
42+
}
43+
44+
// greedy function that locks the shared resource for a long time
45+
func greedy() {
46+
l.m.Lock()
47+
time.Sleep(2 * time.Second)
48+
l.m.Unlock()
49+
}
50+
51+
// normal function that locks the shared resource for a small amount of time
52+
func normal(finish chan bool) {
53+
l.m.Lock()
54+
time.Sleep(100 * time.Millisecond)
55+
finish <- true
56+
l.m.Unlock()
57+
}
58+
59+
func Test_Starvation(t *testing.T) {
60+
create := make(chan bool)
61+
finish := make(chan bool)
62+
63+
// register created/finished thread counters
64+
go func() {
65+
for {
66+
select {
67+
case <-create:
68+
thr.create()
69+
case <-finish:
70+
thr.finish()
71+
}
72+
}
73+
}()
74+
75+
threads := time.Tick(100 * time.Millisecond)
76+
stop := time.After(5 * time.Second)
77+
greed := time.After(2 * time.Second)
78+
79+
loop:
80+
for {
81+
select {
82+
case <-greed:
83+
// start a single greedy go routine after 2s
84+
go greedy()
85+
case <-threads:
86+
// start a new normal go routine every 100ms
87+
go normal(finish)
88+
create <- true
89+
case <-stop:
90+
// wait 100ms to let normal go routines finish and break the loop after 5s
91+
time.Sleep(100 * time.Millisecond)
92+
break loop
93+
}
94+
}
95+
96+
c, f := thr.values()
97+
if c != f {
98+
// assert that number of finished threads is different than the number of created threads
99+
t.Logf("number of created threads: %v", c)
100+
t.Logf("number of finished threads: %v", f)
101+
t.Fatal()
102+
}
103+
}

0 commit comments

Comments
 (0)