Golang在带有频道的goroutine中暂停一个循环

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)

  • 很好的触摸,在暂停状态下退出处理:) (2认同)

Siu*_*ji- 5

问题:

Amd 的回答本质上是用 Go 的select语句构建的状态机。我注意到的一个问题是,当您添加更多功能(如“快进”、“慢动作”等)时,case必须select在“暂停”中添加更多s case

nil渠道接收:

在 Go 中,从(或发送到)一个nil通道会导致“永远阻塞”。这实际上是实现以下技巧的一个非常重要的特性:在for-select模式中,如果将 a 设置case channelnilcase则在下一次迭代中将不会匹配相应的。换句话说,case是“禁用”。

从封闭通道接收:

在 Go 中,从关闭的通道接收总是立即返回。因此,您可以用default case一个持有封闭通道的变量替换您的。当变量保持关闭通道时,它的行为类似于default case; 但是,当变量保持时nilcase永远不会匹配,具有“暂停”行为。

我的想法:

  • 修改您的default案例:改为从关闭的频道读取。(上面解释过);
  • 备份关闭的通道。当pause需要,设置“缺省的情况下,信道”来nil; 当play需要,将其设置为备份;
  • 做一个“continue”通道,让select语句在赋值后重新读取变量;
  • 实际上,“退出”通道可以重用为“继续”通道:struct{}{}需要“继续”时发送;close()当需要“退出”时;
  • 将资源封装在闭包中,并确保清理完成;
  • 确保当start()尚未调用时,不创建任何通道或 go 例程,以防止泄漏。

我的实现(也可在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")
}
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 之后,并且在接收到任何先前发送的值之后,接收操作将在不阻塞的情况下返回通道类型的零值。多值接收操作返回接收到的值以及通道是否关闭的指示。

  • 你让它变得如此复杂和复杂。 (2认同)