I am trying to write a event listener and try to control a state flow inside the listener. I knew I miss some principle of the usage of channel, and the code may looks stupid. However, I will be appreciated if someone can help me to understand what my mistake is and how to improve it.
This code can not work:
package main
import (
"fmt"
"time"
)
type A struct {
count int
ch chan bool
exit chan bool
}
func (this *A) Run() {
for {
select {
case <-this.ch:
this.handler()
case <-this.exit:
return
default:
time.Sleep(20 * time.Millisecond)
}
}
}
func (this *A) handler() {
println("hit me")
if this.count > 2 {
this.exit <- true
}
fmt.Println(this.count)
this.count += 1
}
func (this *A) Hit() {
this.ch <- true
}
func main() {
a := &A{}
a.ch = make(chan bool)
a.exit = make(chan bool)
go a.Hit()
go a.Hit()
go a.Hit()
go a.Hit()
a.Run()
fmt.Println("s")
}
it raise error:
hit me
0
hit me
1
hit me
2
hit me
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.(*A).handler(0x2101bf000)
/Users/yeer/go/src/github.com/athom/practice/channel-controll.go:31 +0x60
main.(*A).Run(0x2101bf000)
/Users/yeer/go/src/github.com/athom/practice/channel-controll.go:19 +0x66
main.main()
/Users/yeer/go/src/github.com/athom/practice/channel-controll.go:50 +0xed
exit status 2
However, this code works:
package main
import (
"fmt"
"time"
)
type A struct {
count int
ch chan bool
exit chan bool
}
func (this *A) Run() {
for {
select {
case <-this.ch:
this.handler()
case <-this.exit:
return
default:
time.Sleep(20 * time.Millisecond)
}
}
}
func (this *A) handler() {
println("hit me")
}
func (this *A) Hit() {
this.ch <- true
if this.count > 2 {
this.exit <- true
}
fmt.Println(this.count)
this.count += 1
}
func main() {
a := &A{}
a.ch = make(chan bool)
a.exit = make(chan bool)
go a.Hit()
go a.Hit()
go a.Hit()
go a.Hit()
a.Run()
fmt.Println("s")
}
Why can not trigger another channel inside a same level channel handler?
Your code deadlocks because when you send on the exit channel this.exit <- true
that is from the same goroutine that you are receiving from that channel and there is no way that will ever complete.
Probably the most sensible thing to do is to replace the exit channel with a boolean flag. If you do that then it works fine.
type A struct {
count int
ch chan bool
exit bool
}
func (this *A) Run() {
for !this.exit {
select {
case <-this.ch:
this.handler()
default:
time.Sleep(20 * time.Millisecond)
}
}
}
func (this *A) handler() {
println("hit me")
if this.count > 2 {
this.exit = true
}
fmt.Println(this.count)
this.count += 1
}
func (this *A) Hit() {
this.ch <- true
}
func main() {
a := &A{}
a.ch = make(chan bool)
go a.Hit()
go a.Hit()
go a.Hit()
go a.Hit()
a.Run()
fmt.Println("Done")
}
Another alternative would be to run each handler in its own go routine with go this.handler()
func (this *A) Run() {
for {
select {
case <-this.ch:
go this.handler() // add go here
case <-this.exit:
return
default:
time.Sleep(20 * time.Millisecond)
}
}
}
And finally you could buffer the exit channel
func main() {
a := &A{}
a.ch = make(chan bool)
a.exit = make(chan bool, 5) // add buffer here
go a.Hit()
go a.Hit()
go a.Hit()
go a.Hit()
a.Run()
fmt.Println("Done")
}
You should use the Time.After() for the wait in the loop:
select {
case <-this.ch:
go this.handler() // add go here
case <-this.exit:
return
case <-time.After(20 * time.Millisecond)