如何停止goroutine

4

我有一个goroutine调用一个函数,并带有一个特殊参数,我想启动或停止这个goroutine。我的问题是,这段代码从未停止我的goroutine,它每次都会创建一个新的任务。

quit := make(chan bool)
run := make(chan bool)

    go func() {
        for {
            select {
            case <-quit:
                close(run)
            case <-run:
                myFunc(c)
            default:
            }
        }
    }()

    if x == true {
        quit <- true
    } else {
        run <- true
    }

如何停止我的例行程序?


1
如果您正在使用布尔值,为什么要创建两个通道? - Aurelia
3个回答

1
当您关闭run通道时,case <-run将始终触发:监听关闭的通道会立即返回零值。
如果您想停止goroutine,则应在获取<-quit信号后返回。
顺便提一下,您的default:子句使for循环主动工作,您应该摆脱它(仍然会同时监听两个通道)。

1
这是一个独立的可运行版本,展示了这样一个信令系统可能如何实现。
package main

import (
    "time"
    "log"
)

func main() {
    statusChannel := make(chan bool)
    go applicationLoop(statusChannel)

    // reasonably random outcome for testing
    if time.Now().Unix() % 2 == 0 {
        statusChannel<-true
    } else {
        statusChannel<-false
    }

    for {
        // busy loop for testing
        time.Sleep(1000)
    }
}

func applicationLoop(statusChannel chan bool) {
    defer close(statusChannel)
    for {
        log.Printf("waiting for signal...\n")
        shouldContinue := <-statusChannel
        if !shouldContinue {
            log.Print("received false, breaking...\n")
            break
        }
        // run your code here
        // you should use a second channel to return results, as the channel is not buffered
        log.Print("working...\n")
    }
}

请注意,当 statusChannel 没有处于监听状态时发送值将会导致示例出现问题。请使用带缓冲的通道或者在 goroutine 回到监听信号时向 main 发送信号的通道。

太好了!它运行了,谢谢! - user5646735
没有必要从通道中读取值,然后再检查它。你可以直接在if语句中从通道中读取:if !<-statusChannel {。示例:https://play.golang.org/p/3q5jpC19rW。你还可以通过无条件发送当前用作if语句条件的布尔表达式的结果来压缩状态通道上的发送操作。 - Kaedys

1
这个问题有两个部分。
首先,我们需要以某种方式停止子Goroutine,即使父Goroutine停止,所有子Goroutine也应该得到通知并停止 - 这是一个向下而不是向上的停止信号层次结构。
另一方面,父Goroutine需要等待其子Goroutine完成。否则,我们可能会在某些Goroutine正确完成之前从Goroutine返回甚至退出应用程序。
为了简单起见,我们忽略实现错误处理、超时等内容。
对于处理第一个问题,我们使用context.Context,它为我们提供了一个很好的执行上下文处理工具层次结构;对于解决第二个问题,我们使用sync.WaitGroup,它允许我们等待一组Goroutine完成其任务。一个简单的演示如下:
func main() {
    all := &sync.WaitGroup{}
    rootCtx, rootCancel := context.WithCancel(context.Background())

    all.Add(1)
    go level1(rootCtx, all)

    // just to simulate stop, we could use an os signal instead
    // app ends after 3 seconds
    go func() {
        time.Sleep(time.Second * 3)
        rootCancel()
    }()

    all.Wait()
}

func level1(parent context.Context, all *sync.WaitGroup) {
    defer all.Done()
    l1Ctx, l1Cancel := context.WithCancel(parent)
    defer l1Cancel()

    for i := 0; i < 3; i++ {
        all.Add(1)
        go level2(l1Ctx, all)
    }

    for {
        select {
        case <-parent.Done():
            return
        // other cases if any,
        // this is a sample
        case <-time.After(time.Second):
            log.Println(`level1`)
        }
    }
}

func level2(parent context.Context, all *sync.WaitGroup) {
    defer all.Done()
    for {
        select {
        case <-parent.Done():
            return
        case <-time.After(time.Second):
            log.Println(`level2`)
        }
    }
}

这给我们一些输出,例如:
[  info ] level2
[  info ] level2
[  info ] level2
[  info ] level1
[  info ] level2
[  info ] level1
[  info ] level2
[  info ] level2

目前没有官方的包提供将context.Contextsync.WaitGroup结合起来的功能。最接近的是errgroup,可以通过一些技巧实现类似的功能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接