Synchronization queues in Golang

how to use channels to write idiomatic code in Go

Michał Łowicki
Mar 12, 2018 · 6 min read

Problem

func main() {
for i := 0; i < 10; i++ {
go programmer()
}
for i := 0; i < 5; i++ {
go tester()
}
select {} // long day at work...
}
func programmer() {
for {
code()
pingPong()
}
}
func tester() {
for {
test()
pingPong()
}
}
func test() {
work()
}
func code() {
work()
}
func work() {
// Sleep up to 10 seconds.
time.Sleep(time.Duration(rand.Intn(10000)) * time.Millisecond)
}
func pingPong() {
// Sleep up to 2 seconds.
time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
}
func programmer() {
for {
code()
fmt.Println("Programmer starts")
pingPong()
fmt.Println("Programmer ends")
}
}
func tester() {
for {
test()
fmt.Println("Tester starts")
pingPong()
fmt.Println("Tester ends")
}
}
> go run pingpong.go
Tester starts
Programmer starts
Programmer starts
Tester ends
Programmer ends
Programmer starts
Programmer ends
Programmer ends
Tester starts
Programmer starts
Tester ends
Programmer ends
Tester starts
Programmer starts
Programmer ends
Tester ends
Programmer starts
Tester starts
Tester ends
Programmer ends
Programmer starts
Tester starts
Programmer ends
Tester ends

Solution #1

func tester(q *queue.Queue) {
for {
test()
q.StartT()
fmt.Println("Tester starts")
pingPong()
fmt.Println("Tester ends")
q.EndT()
}
}
func programmer(q *queue.Queue) {
for {
code()
q.StartP()
fmt.Println("Programmer starts")
pingPong()
fmt.Println("Programmer ends")
q.EndP()
}
}
func main() {
q := queue.New()
for i := 0; i < 10; i++ {
go programmer(q)
}
for i := 0; i < 5; i++ {
go tester(q)
}
select {}
}
package queueimport "sync"type Queue struct {
mut sync.Mutex
numP, numT int
queueP, queueT, doneP chan int
}
func New() *Queue {
q := Queue{
queueP: make(chan int),
queueT: make(chan int),
doneP: make(chan int),
}
return &q
}
func (q *Queue) StartT() {
q.mut.Lock()
if q.numP > 0 {
q.numP -= 1
q.queueP <- 1
} else {
q.numT += 1
q.mut.Unlock()
<-q.queueT
}
}
func (q *Queue) EndT() {
<-q.doneP
q.mut.Unlock()
}
func (q *Queue) StartP() {
q.mut.Lock()
if q.numT > 0 {
q.numT -= 1
q.queueT <- 1
} else {
q.numP += 1
q.mut.Unlock()
<-q.queueP
}
}
func (q *Queue) EndP() {
q.doneP <- 1
}
<-q.queueP
<-q.queueT
func (q *Queue) StartT() {
q.mut.Lock()
if q.numP > 0 {
q.numP -= 1
q.queueP <- 1
} else {
q.numT += 1
q.mut.Unlock()
<-q.queueT
}
}
func (q *Queue) EndT() {
<-q.doneP
q.mut.Unlock()
}
func (q *Queue) EndP() {
q.doneP <- 1
}

Solution #2

package queueconst (
msgPStart = iota
msgTStart
msgPEnd
msgTEnd
)
type Queue struct {
waitP, waitT int
playP, playT bool
queueP, queueT chan int
msg chan int
}
func New() *Queue {
q := Queue{
msg: make(chan int),
queueP: make(chan int),
queueT: make(chan int),
}
go func() {
for {
select {
case n := <-q.msg:
switch n {
case msgPStart:
q.waitP++
case msgPEnd:
q.playP = false
case msgTStart:
q.waitT++
case msgTEnd:
q.playT = false
}
if q.waitP > 0 && q.waitT > 0 && !q.playP && !q.playT {
q.playP = true
q.playT = true
q.waitT--
q.waitP--
q.queueP <- 1
q.queueT <- 1
}
}
}
}()
return &q
}
func (q *Queue) StartT() {
q.msg <- msgTStart
<-q.queueT
}
func (q *Queue) EndT() {
q.msg <- msgTEnd
}
func (q *Queue) StartP() {
q.msg <- msgPStart
<-q.queueP
}
func (q *Queue) EndP() {
q.msg <- msgPEnd
}

Resources

golangspec

A series dedicated to deeply understand Go’s specification…