如何暂停和恢复 goroutine?

How to pause and resume goroutine?

我正在尝试暂停和恢复 groutine。我明白我可以 sleep 运行,但我要找的是像按钮 "pause/resume" 而不是计时器。

这是我的尝试。我正在使用通道的阻塞功能来暂停,并使用 select 根据通道值切换要执行的内容。但是,在我的例子中,输出总是 Running

func main() {
    ctx := wctx{}
    go func(ctx wctx) {
        for {
            time.Sleep(1 * time.Second)
            select {
            case <-ctx.pause:
                fmt.Print("Paused")
                <-ctx.pause
            case <-ctx.resume:
                fmt.Print("Resumed")
            default:
                fmt.Print("Running \n")
            }
        }
    }(ctx)

    ctx.pause <- struct{}{}
    ctx.resume <- struct{}{}
}

type wctx struct {
    pause  chan struct{}
    resume chan struct{}
}

您需要初始化通道,请记住从 nil 通道读取总是阻塞。

带有 default 案例的 select 永远不会阻塞。

这是您程序的修改版本,修复了上述问题:

package main

import (
    "fmt"
    "time"
)

func main() {
    ctx := wctx{
        pause:  make(chan struct{}),
        resume: make(chan struct{}),
    }

    go func(ctx wctx) {
        for {
            select {
            case <-ctx.pause:
                fmt.Println("Paused")
            case <-ctx.resume:
                fmt.Println("Resumed")
            }

            fmt.Println("Running")
            time.Sleep(time.Second)
        }
    }(ctx)

    ctx.pause <- struct{}{}
    ctx.resume <- struct{}{}
}

type wctx struct {
    pause  chan struct{}
    resume chan struct{}
}

A select 有多个就绪案例,伪随机选择一个。因此,如果 goroutine 是 "slow" 来检查这些通道,您可能会在 pauseresume 上发送一个值(假设它们被缓冲)以便从两个通道接收可以准备好,并且 resume 可以首先选择,在以后的迭代中 pause 当 goroutine 不应该再暂停时。

为此,您应该使用由互斥锁同步的 "state" 变量。像这样:

const (
    StateRunning = iota
    StatePaused
)

type wctx struct {
    mu    sync.Mutex
    state int
}

func (w *wctx) SetState(state int) {
    w.mu.Lock()
    defer w.mu.Unlock()
    w.state = state
}

func (w *wctx) State() int {
    w.mu.Lock()
    defer w.mu.Unlock()
    return w.state
}

正在测试:

ctx := &wctx{}
go func(ctx *wctx) {
    for {
        time.Sleep(1 * time.Millisecond)
        switch state := ctx.State(); state {
        case StatePaused:
            fmt.Println("Paused")
        default:
            fmt.Println("Running")
        }
    }
}(ctx)

time.Sleep(3 * time.Millisecond)
ctx.SetState(StatePaused)
time.Sleep(3 * time.Millisecond)
ctx.SetState(StateRunning)
time.Sleep(2 * time.Millisecond)

输出(在 Go Playground 上尝试):

Running
Running
Running
Paused
Paused
Paused
Running
Running