关于通道控制流的困惑

huangapple go评论162阅读模式
英文:

Confusing about channel control flow

问题

我正在为你翻译以下内容:

我正在尝试编写一个事件监听器,并尝试在监听器内部控制状态流程。
我知道我在使用通道的原则方面有些遗漏,代码可能看起来很愚蠢。然而,如果有人能帮助我理解我的错误在哪里以及如何改进它,我将不胜感激。

这段代码无法工作:

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")
}

它会引发错误:

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

然而,这段代码可以工作:

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")
}

为什么不能在同一级别的通道处理程序中触发另一个通道?

英文:

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 (
	&quot;fmt&quot;
	&quot;time&quot;
)

type A struct {
	count int
	ch    chan bool
	exit  chan bool
}

func (this *A) Run() {
	for {
		select {
		case &lt;-this.ch:
			this.handler()
		case &lt;-this.exit:
			return
		default:
			time.Sleep(20 * time.Millisecond)
		}
	}
}

func (this *A) handler() {
	println(&quot;hit me&quot;)
	if this.count &gt; 2 {
		this.exit &lt;- true
	}
	fmt.Println(this.count)
	this.count += 1
}

func (this *A) Hit() {
	this.ch &lt;- true
}

func main() {
	a := &amp;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(&quot;s&quot;)
}

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 (
	&quot;fmt&quot;
	&quot;time&quot;
)

type A struct {
	count int
	ch    chan bool
	exit  chan bool
}

func (this *A) Run() {
	for {
		select {
		case &lt;-this.ch:
			this.handler()
		case &lt;-this.exit:
			return
		default:
			time.Sleep(20 * time.Millisecond)
		}
	}
}

func (this *A) handler() {
	println(&quot;hit me&quot;)
}

func (this *A) Hit() {
	this.ch &lt;- true
	if this.count &gt; 2 {
		this.exit &lt;- true
	}
	fmt.Println(this.count)
	this.count += 1
}

func main() {
	a := &amp;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(&quot;s&quot;)
}

Why can not trigger another channel inside a same level channel handler?

答案1

得分: 0

你的代码发生了死锁,因为当你在退出通道 this.exit <- true 上发送消息时,这是在同一个 goroutine 中接收该通道的消息,而且没有办法完成。

可能最明智的做法是用布尔标志替换退出通道。如果你这样做,代码就可以正常工作。

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")
}

另一种选择是在每个处理程序中使用单独的 goroutine 运行 go this.handler()

func (this *A) Run() {
    for {
        select {
        case <-this.ch:
            go this.handler() // 在这里添加 go
        case <-this.exit:
            return
        default:
            time.Sleep(20 * time.Millisecond)
        }
    }
}

最后,你可以给退出通道添加缓冲区

func main() {
    a := &A{}
    a.ch = make(chan bool)
    a.exit = make(chan bool, 5) // 在这里添加缓冲区

    go a.Hit()
    go a.Hit()
    go a.Hit()
    go a.Hit()
    a.Run()

    fmt.Println("Done")
}
英文:

Your code deadlocks because when you send on the exit channel this.exit &lt;- 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 &lt;-this.ch:
			this.handler()
		default:
			time.Sleep(20 * time.Millisecond)
		}
	}
}

func (this *A) handler() {
	println(&quot;hit me&quot;)
	if this.count &gt; 2 {
		this.exit = true
	}
	fmt.Println(this.count)
	this.count += 1
}

func (this *A) Hit() {
	this.ch &lt;- true
}

func main() {
	a := &amp;A{}
	a.ch = make(chan bool)

	go a.Hit()
	go a.Hit()
	go a.Hit()
	go a.Hit()
	a.Run()

	fmt.Println(&quot;Done&quot;)
}

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 &lt;-this.ch:
			go this.handler() // add go here
		case &lt;-this.exit:
			return
		default:
			time.Sleep(20 * time.Millisecond)
		}
	}
}

And finally you could buffer the exit channel

Play

func main() {
	a := &amp;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(&quot;Done&quot;)
}

答案2

得分: 0

你应该在循环中使用Time.After()来进行等待:

    select {
    case <-this.ch:
        go this.handler() // 在这里添加go关键字
    case <-this.exit:
        return
    case <-time.After(20 * time.Millisecond)
英文:

You should use the Time.After() for the wait in the loop:

    select {
    case &lt;-this.ch:
        go this.handler() // add go here
    case &lt;-this.exit:
        return
    case &lt;-time.After(20 * time.Millisecond)

huangapple
  • 本文由 发表于 2013年11月17日 14:27:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/20027901.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定