我有一个作为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
}
此函数使用通道,因此可以打破循环(循环可以是有限的或无限的)
我想要实现的是暂停循环执行的方法,当然还能恢复它。
我正在考虑将另一个案例添加到我在另一个频道pause
上收听的选择条件。如果执行该案例,它将进入一个无效的新无限循环。然后,它将需要与之前使用resume
通道相同的系统来打破此循环。
此致
答案 0 :(得分:5)
使用频道在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
输出:
1
2
3
4
pause
play
5
6
7
8
pause
play
9
10
11
12
done
答案 1 :(得分:3)
Amd's answer本质上是一个使用Go的select
语句构建的状态机。我注意到的一个问题是,当您添加更多功能(例如“快进”,“慢动作”等)时,必须在“暂停”中将case
添加到select
。 case
。
nil
频道接收:在Go中,从nil
频道接收(或发送)会导致“永久阻止”。这实际上是实现以下技巧的一个非常重要的功能:在for
- select
模式中,如果您将case
channel
设置为nil
,相应的case
将在下一次迭代中不匹配。换句话说,case
被“禁用”。
在Go中,从封闭频道接收始终立即返回。因此,您可以将default
case
替换为持有封闭频道的变量。当变量保持闭合通道时,它的行为类似于default
case
;但是,当变量保持nil
时,case
永远不会匹配,具有“暂停”行为。
default
案例:改为从已关闭的频道中读取。 (上文解释); pause
,请将“默认案例渠道”设置为nil
;如果需要play
,请将其设置为备份; select
语句在分配后重新读取变量; struct{}{}
;当需要“退出”时close()
; start()
时,不会创建任何频道或转到例程,以防止泄密。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")
}
如果你真的想要实现“快进”和“慢动作”,只需:
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
发送或关闭已关闭的频道会导致运行时出现紧急情况。关闭零通道也会导致运行时恐慌。在调用close之后,在收到任何先前发送的值之后,接收操作将返回通道类型的零值而不会阻塞。多值接收操作返回接收值以及信道是否关闭的指示。
答案 2 :(得分:0)
根据上面使用频道的@ user6169399修改
package main
import (
"fmt"
"time"
"sync"
)
var i int
func work() {
time.Sleep(250 * time.Millisecond)
i++
fmt.Println(i)
}
func routine(command <- chan string, wg *sync.WaitGroup) {
defer wg.Done()
var status = "play"
for {
select {
case cmd := <- command:
fmt.Println(cmd)
switch cmd {
case "stop":
return
case "pause":
status = "pause"
default:
status = "play"
}
default:
if status == "play" {
work()
}
}
}
}
func main() {
var wg sync.WaitGroup
wg.Add(1)
command := make(chan string)
go routine(command, &wg)
time.Sleep(1 * time.Second)
command <- "pause"
time.Sleep(1 * time.Second)
command <- "play"
time.Sleep(1 * time.Second)
command <- "stop"
wg.Wait()
}