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

问题描述 投票:1回答:3

我有一个作为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通道相同的系统来打破这个循环。

你怎么看 ?有没有更好的方法来实现我的需求?

问候

loops go channel pause
3个回答
3
投票

The problem:

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

Receiving from nil channels:

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

Receiving from closed channels:

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

My ideas:

  • 修改你的default案例:改为从封闭的渠道读取。 (上文解释);
  • 备份已关闭的频道。当需要pause时,将“默认案例通道”设置为nil;当需要play时,将其设置为备份;
  • 建立一个“继续”通道,要求select语句在赋值后重新读取变量;
  • 事实上,“退出”频道可以作为“继续”频道重复使用:当需要“继续”时发送struct{}{};当需要“退出”时,close();
  • 将资源封装在闭包中,并确保完成清理;
  • 确保在尚未调用start()时,不会创建任何通道或执行例程,以防止泄漏。

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:

如果你真的想要实现“快进”和“慢动作”,那么简单地说:

  • 将魔法250重构为变量;
  • prepare()返回一个用于设置变量的闭包,并将struct{}{}发送到chControl

请注意,这种简单的情况会忽略“竞争条件”。

References:

https://golang.org/ref/spec#Send_statements

关闭通道上的发送通过导致运行时恐慌而继续。在零通道上发送永久阻止。

https://golang.org/ref/spec#Receive_operator

从零通道接收永远阻止。在关闭的通道上的接收操作总是可以立即进行,在接收到任何先前发送的值之后产生元素类型的零值。

https://golang.org/ref/spec#Close

发送或关闭已关闭的通道会导致运行时出现紧急情况。关闭零通道也会导致运行时恐慌。在调用close之后,在收到任何先前发送的值之后,接收操作将返回通道类型的零值而不会阻塞。多值接收操作返回接收值以及信道是否关闭的指示。


5
投票

使用通道暂停goroutine中的循环,使用playpausequit通道,如下工作示例代码:

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

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()
}
© www.soinside.com 2019 - 2024. All rights reserved.