通道控制流程混乱

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.

Play

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()

Play

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

Play

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)