Wil*_*ier 1 loops channel go pause
我有一个作为goroutine启动的功能:
func (bt *BlinkyTape) finiteLoop(frames []Frame, repeat int, delay time.Duration) {
bt.isPlaying = true
L:
for i := 0; i < repeat; i++ {
select {
case <-bt.stop:
break L
default:
bt.playFrames(frames, delay)
}
}
bt.isPlaying = false
}
Run Code Online (Sandbox Code Playgroud)
此函数使用通道,因此可以打破循环(循环可以是有限的或无限的)
我想要实现的是一种暂停循环执行的方法,当然还能恢复它.
我正在考虑将另一个案例添加到我在另一个频道上收听的选择条件pause
.如果执行该案例,它将进入一个无效的新无限循环.然后它将需要与之前相同的系统和一个resume
通道来打破这个循环.
你怎么看 ?有没有更好的方法来实现我的需求?
问候
小智 6
暂停的循环中具有通道,使用的goroutine play
,pause
并且quit
像这样工作的示例代码信道:
package main
import "fmt"
import "time"
import "sync"
func routine() {
for {
select {
case <-pause:
fmt.Println("pause")
select {
case <-play:
fmt.Println("play")
case <-quit:
wg.Done()
return
}
case <-quit:
wg.Done()
return
default:
work()
}
}
}
func main() {
wg.Add(1)
go routine()
time.Sleep(1 * time.Second)
pause <- struct{}{}
time.Sleep(1 * time.Second)
play <- struct{}{}
time.Sleep(1 * time.Second)
pause <- struct{}{}
time.Sleep(1 * time.Second)
play <- struct{}{}
time.Sleep(1 * time.Second)
close(quit)
wg.Wait()
fmt.Println("done")
}
func work() {
time.Sleep(250 * time.Millisecond)
i++
fmt.Println(i)
}
var play = make(chan struct{})
var pause = make(chan struct{})
var quit = make(chan struct{})
var wg sync.WaitGroup
var i = 0
Run Code Online (Sandbox Code Playgroud)
输出:
1
2
3
4
pause
play
5
6
7
8
pause
play
9
10
11
12
done
Run Code Online (Sandbox Code Playgroud)
Amd 的回答本质上是用 Go 的select
语句构建的状态机。我注意到的一个问题是,当您添加更多功能(如“快进”、“慢动作”等)时,case
必须select
在“暂停”中添加更多s case
。
nil
渠道接收:在 Go 中,从(或发送到)一个nil
通道会导致“永远阻塞”。这实际上是实现以下技巧的一个非常重要的特性:在for
-select
模式中,如果将 a 设置case
channel
为nil
,case
则在下一次迭代中将不会匹配相应的。换句话说,case
是“禁用”。
在 Go 中,从关闭的通道接收总是立即返回。因此,您可以用default
case
一个持有封闭通道的变量替换您的。当变量保持关闭通道时,它的行为类似于default
case
; 但是,当变量保持时nil
,case
永远不会匹配,具有“暂停”行为。
default
案例:改为从关闭的频道读取。(上面解释过);pause
需要,设置“缺省的情况下,信道”来nil
; 当play
需要,将其设置为备份;select
语句在赋值后重新读取变量;struct{}{}
需要“继续”时发送;close()
当需要“退出”时;start()
尚未调用时,不创建任何通道或 go 例程,以防止泄漏。package main
import "fmt"
import "time"
import "sync"
func prepare() (start, pause, play, quit, wait func()) {
var (
chWork <-chan struct{}
chWorkBackup <-chan struct{}
chControl chan struct{}
wg sync.WaitGroup
)
routine := func() {
defer wg.Done()
i := 0
for {
select {
case <-chWork:
fmt.Println(i)
i++
time.Sleep(250 * time.Millisecond)
case _, ok := <-chControl:
if ok {
continue
}
return
}
}
}
start = func() {
// chWork, chWorkBackup
ch := make(chan struct{})
close(ch)
chWork = ch
chWorkBackup = ch
// chControl
chControl = make(chan struct{})
// wg
wg = sync.WaitGroup{}
wg.Add(1)
go routine()
}
pause = func() {
chWork = nil
chControl <- struct{}{}
fmt.Println("pause")
}
play = func() {
fmt.Println("play")
chWork = chWorkBackup
chControl <- struct{}{}
}
quit = func() {
chWork = nil
close(chControl)
fmt.Println("quit")
}
wait = func() {
wg.Wait()
}
return
}
func sleep() {
time.Sleep(1 * time.Second)
}
func main() {
start, pause, play, quit, wait := prepare()
sleep()
start()
fmt.Println("start() called")
sleep()
pause()
sleep()
play()
sleep()
pause()
sleep()
play()
sleep()
quit()
wait()
fmt.Println("done")
}
Run Code Online (Sandbox Code Playgroud)
如果你真的想实现“快进”和“慢动作”,只需:
250
为一个变量;prepare()
用于设置变量的闭包并发struct{}{}
送到chControl
.请注意,对于这个简单的情况,忽略了“竞争条件”。
https://golang.org/ref/spec#Send_statements
关闭通道上的发送会导致运行时恐慌。在 nil 通道上发送永远阻塞。
https://golang.org/ref/spec#Receive_operator
从 nil 通道接收永远阻塞。关闭通道上的接收操作总是可以立即进行,在接收到任何先前发送的值后产生元素类型的零值。
https://golang.org/ref/spec#Close
发送到或关闭关闭的通道会导致运行时恐慌。关闭 nil 通道也会导致运行时恐慌。在调用 close 之后,并且在接收到任何先前发送的值之后,接收操作将在不阻塞的情况下返回通道类型的零值。多值接收操作返回接收到的值以及通道是否关闭的指示。