Golang 中主流程要控制某个协程的暂停与继续,需要两个通道分别接收来自主流程的通知,并在协程中始终监听这两个通知。例如:

package main

import (
    "fmt"
    "time"
)

func main() {
    // 创建一个通道
    chPause := make(chan struct{})
    chResume := make(chan struct{})

    // 启动一个 goroutine
    go func() {
        // 初始化 isPaused
        isPaused := false

        // 等待通知
        for {
            select {
            case <-chPause:
                // 暂停执行
                isPaused = true
                fmt.Println("已暂停。")
            case <-chResume:
                // 继续执行
                isPaused = false
                fmt.Println("继续执行。")
            default:
                // 暂停执行
                if isPaused {
                    continue
                }
                // 执行任务
                fmt.Println("执行任务...")
                time.Sleep(time.Millisecond * 500)
            }
        }
    }()

    // 发送通知
    time.Sleep(time.Second * 2)
    chPause <- struct{}{}
    time.Sleep(time.Second * 2)
    chResume <- struct{}{}
    time.Sleep(time.Second)
}

以上代码中,对于已经开始执行的任务无法暂停,只能做到收到暂停通知后不再继续执行后续任务。再考虑到chPausechResume均为无缓冲通道,这意味着任务未执行完毕时,暂时不接收chPausechResume,即发送端会被阻塞。此时,可以认为,只要暂停信号成功送入,即表示之前的任务已暂停,且不会执行后续的任务。若之前任务未暂停,则需要待之前的任务执行完毕后才会送入。即,暂停信号的送入和成功暂停是同步的。

如果想要无阻塞送入暂停信号,可以为chPause设置缓冲,并在成功发送通知后阻止再次送入信号。

相应地,由于送入暂停信号和实际暂停相分离,需要设置回调机制通知送入信号一方已成功暂停,以实现送入暂停信号和成功暂停的完整异步逻辑。

例如:

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

type CoroutineControl struct {
    pauseChan    chan struct{}
    resumeChan   chan struct{}
    pauseNotify  chan struct{}
    resumeNotify chan struct{}
    isPaused     bool
    mu           sync.Mutex
}

func NewCoroutineControl() *CoroutineControl {
    return &CoroutineControl{
        pauseChan:    make(chan struct{}),
        resumeChan:   make(chan struct{}),
        pauseNotify:  make(chan struct{}),
        resumeNotify: make(chan struct{}),
    }
}

func (cc *CoroutineControl) Run(ctx context.Context) {
    go func() {
        defer fmt.Println("canceled.")
        for {
            select {
            case <-cc.pauseChan:
                cc.mu.Lock()
                cc.isPaused = true
                fmt.Println("Paused.")
                close(cc.pauseNotify)
                cc.mu.Unlock()
                select {
                case <-cc.resumeChan:
                    cc.mu.Lock()
                    cc.isPaused = false
                    fmt.Println("Resumed.")
                    close(cc.resumeNotify)
                    cc.mu.Unlock()
                case <-ctx.Done():
                    return
                }
            case <-ctx.Done():
                return
            default:
                cc.mu.Lock()
                if !cc.isPaused {
                    fmt.Println("Working...")
                }
                cc.mu.Unlock()
                time.Sleep(time.Millisecond * 500)
            }
        }
    }()
}

func (cc *CoroutineControl) Pause() {
    cc.mu.Lock()
    if !cc.isPaused {
        cc.pauseNotify = make(chan struct{})
        cc.mu.Unlock()
        cc.pauseChan <- struct{}{}
        <-cc.pauseNotify
    } else {
        cc.mu.Unlock()
    }
}

func (cc *CoroutineControl) Resume() {
    cc.mu.Lock()
    if cc.isPaused {
        cc.resumeNotify = make(chan struct{})
        cc.mu.Unlock()
        cc.resumeChan <- struct{}{}
        <-cc.resumeNotify
    } else {
        cc.mu.Unlock()
    }
}

func main() {
    control := NewCoroutineControl()
    ctx, cancel := context.WithCancel(context.Background())

    control.Run(ctx)

    time.Sleep(time.Second)
    control.Pause()

    time.Sleep(time.Second)
    control.Resume()

    time.Sleep(time.Second * 2)
    control.Pause()

    fmt.Println("waiting for being canceled.")
    // 模拟在其他地方调用cancel来取消协程
    time.Sleep(time.Second * 2)
    cancel()

    // 等待协程退出
    time.Sleep(time.Second)
}

vistart
0 声望0 粉丝

未破壳的雏。