I have a function that is launched as a 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 a loop in a goroutine with channels, use play
, pause
and quit
channels like this working sample code:
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
output:
1
2
3
4
pause
play
5
6
7
8
pause
play
9
10
11
12
done
Amd's answer is essentially a state machine built with Go's select
statement. One problem I noticed is that when you add more functionalities (like "fast forward", "slow motion", etc.), more case
s have to be added to the select
in the "pause" case
.
nil
channels: In Go, receiving from (or sending to) a nil
channel results in "blocking forever". This in fact is a very important feature to implement the following trick: In a for
- select
pattern, if you set a case
channel
to nil
, the corresponding case
will not be matched in the next iteration. In other words, the case
is "disabled".
In Go, receiving from a closed channel always returns immediately. Therefore, you may replace your default
case
by a variable holding a closed channel. When the variable holds the closed channel, it behaves like the default
case
; However, when the variable holds nil
, the case
is never matched, having the "pause" behavior.
default
case: read from a closed channel instead. (explained above); pause
is needed, set the "default case channel" to nil
; when play
is needed, set it to the backup;select
statement to re-read the variables after assignment;struct{}{}
when "continue" is needed; close()
when "quit" is needed; start()
is not yet called, no channels or go routines are created, in order to prevent leaks.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")
}
If you really want to implement "fast forward" and "slow motion", simply:
250
to a variable;prepare()
used to set the variable and send struct{}{}
to chControl
.Please be reminded that "race conditions" are ignored for this simple case.
https://golang.org/ref/spec#Send_statements
A send on a closed channel proceeds by causing a run-time panic. A send on a nil channel blocks forever.
https://golang.org/ref/spec#Receive_operator
Receiving from a nil channel blocks forever. A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.
https://golang.org/ref/spec#Close
Sending to or closing a closed channel causes a run-time panic. Closing the nil channel also causes a run-time panic. After calling close, and after any previously sent values have been received, receive operations will return the zero value for the channel's type without blocking. The multi-valued receive operation returns a received value along with an indication of whether the channel is closed.
Modified according to @user6169399 above that uses a channel
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()
}
I have a function that is launched as a 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
}
This function uses channels so it is possible to break the loop (loop can be finite or infinite)
What I would like to implement is a way to pause the execution of the loop and of course being able to resume it.
I was thinking to add another case to the select condition where I listen on another channel pause
. If the case is executed, it enter in a new infinite loop that does nothing. Then it will need the same system as previously with a resume
channel to break this loop.
What do you think ? Is there a better way to achieve what I need ?
Regards
The above code when converted to a class becomes more useful and allows multiple players concurrently when used in a service. Below is same example written as a class.
// The class methods
type Player interface {
Play()
Pause()
Stop()
Routine()
}
// data handled by class as required
type action struct {
uid string
command chan string
wg *sync.WaitGroup
i int
}
// A map to hold instances of above class
var playList = make(map[string]action)
// Global object of type action
var playAction action
// implementation of methods
func (ch action) Play() {
fmt.Println(ch.uid) // display unique id
ch.command <- "play" // update the channel status
}
func (ch action) Pause() {
fmt.Println(ch.uid)
ch.command <- "pause"
}
func (ch action) Stop() {
fmt.Println(ch.uid)
ch.command <- "stop"
}
func (ch action) Routine() {
defer ch.wg.Done()
fmt.Println(ch.uid)
var status = "play" // initial status is always play
for {
select {
case cmd := <-ch.command:
fmt.Println(cmd)
switch cmd {
case "stop":
return
case "pause":
status = "pause"
default:
status = "play"
}
default:
if status == "play" {
work()
}
}
}
}
func main() {
// This could be part of some service
// some unique id
var uid string "Object1"
var wg sync.WaitGroup
wg.Add(1)
command := make(chan string)
i := 0
playAction = action{uid,command, &wg, i}
playList[uid] = playAction
go playList[uid].Routine()
command <- "play" // update the channel
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.