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

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 <kbd>The Go Playground</kbd>):

    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.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(2条)

报告相同问题?

悬赏问题

  • ¥15 孟德尔随机化结果不一致
  • ¥15 apm2.8飞控罗盘bad health,加速度计校准失败
  • ¥15 求解O-S方程的特征值问题给出边界层布拉休斯平行流的中性曲线
  • ¥15 谁有desed数据集呀
  • ¥20 手写数字识别运行c仿真时,程序报错错误代码sim211-100
  • ¥15 关于#hadoop#的问题
  • ¥15 (标签-Python|关键词-socket)
  • ¥15 keil里为什么main.c定义的函数在it.c调用不了
  • ¥50 切换TabTip键盘的输入法
  • ¥15 可否在不同线程中调用封装数据库操作的类