duanguane1670
2016-08-05 23:04
浏览 148
已采纳

Golang在带有通道的goroutine中暂停循环

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

  • 写回答
  • 好问题 提建议
  • 关注问题
  • 收藏
  • 邀请回答

3条回答 默认 最新

  • dongyou9818 2016-08-07 19:24
    已采纳

    The problem:

    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 cases have to be added to the select in the "pause" case.

    Receiving from 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".

    Receiving from closed channels:

    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.

    My ideas:

    • Modify your default case: read from a closed channel instead. (explained above);
    • Make a backup of the closed channel. When pause is needed, set the "default case channel" to nil; when play is needed, set it to the backup;
    • Make a "continue" channel to ask the select statement to re-read the variables after assignment;
    • In fact, the "quit" channel can be reused as the "continue" channel: send struct{}{} when "continue" is needed; close() when "quit" is needed;
    • Encapsulate the resources in closures, and ensure that cleanup is done;
    • Ensure that when start() is not yet called, no channels or go routines are created, in order to prevent leaks.

    My implementation (also available at The Go Playground):

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

    Extras:

    If you really want to implement "fast forward" and "slow motion", simply:

    • Refactor the magic 250 to a variable;
    • Return one more closure from prepare() used to set the variable and send struct{}{} to chControl.

    Please be reminded that "race conditions" are ignored for this simple case.

    References:

    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.

    已采纳该答案
    评论
    解决 无用
    打赏 举报
  • drvc63490 2016-08-06 05:08

    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
    
    评论
    解决 无用
    打赏 举报
  • dounao5856 2018-03-06 18:50

    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()
    }
    
    评论
    解决 无用
    打赏 举报

相关推荐 更多相似问题