繁体   English   中英

如何停止一个goroutine

[英]How to stop a goroutine

我有一个调用方法的 goroutine,并在通道上传递返回值:

ch := make(chan int, 100)
go func(){
    for {
        ch <- do_stuff()
    }
}()

我如何停止这样的 goroutine?

通常,您向 goroutine 传递一个(可能是单独的)信号通道。 当您希望 goroutine 停止时,该信号通道用于推送一个值。 goroutine 定期轮询该通道。 一旦检测到信号,它就会退出。

quit := make(chan bool)
go func() {
    for {
        select {
        case <- quit:
            return
        default:
            // Do other stuff
        }
    }
}()

// Do stuff

// Quit goroutine
quit <- true

编辑:在意识到您的问题是关于将值发送到 goroutine 中的 chan 之前,我匆忙写下了这个答案。 下面的方法可以与上面建议的附加 chan 一起使用,或者利用您已经拥有的 chan 是双向的事实,您可以只使用一个...

如果您的 goroutine 仅用于处理来自 chan 的项目,您可以使用“close”内置函数和特殊的通道接收表单。

也就是说,一旦你在 chan 上发送完项目,你就关闭它。 然后在你的 goroutine 中,你会得到一个额外的参数给接收操作符,显示通道是否已经关闭。

这是一个完整的示例(waitgroup 用于确保该过程继续进行,直到 goroutine 完成):

package main

import "sync"
func main() {
    var wg sync.WaitGroup
    wg.Add(1)

    ch := make(chan int)
    go func() {
        for {
            foo, ok := <- ch
            if !ok {
                println("done")
                wg.Done()
                return
            }
            println(foo)
        }
    }()
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)

    wg.Wait()
}

你不能从外面杀死一个 goroutine。 您可以向 goroutine 发出信号以停止使用通道,但是 goroutine 无法进行任何类型的元管理。 Goroutines 旨在合作解决问题,因此杀死一个行为不端的人几乎永远不会是一个适当的回应。 如果您想要隔离以提高健壮性,您可能需要一个过程。

通常,您可以创建一个通道并在 goroutine 中接收一个停止信号。

在这个例子中有两种创建频道的方法。

  1. 渠道

  2. 上下文 在示例中,我将演示context.WithCancel

第一个演示,使用channel

package main

import "fmt"
import "time"

func do_stuff() int {
    return 1
}

func main() {

    ch := make(chan int, 100)
    done := make(chan struct{})
    go func() {
        for {
            select {
            case ch <- do_stuff():
            case <-done:
                close(ch)
                return
            }
            time.Sleep(100 * time.Millisecond)
        }
    }()

    go func() {
        time.Sleep(3 * time.Second)
        done <- struct{}{}
    }()

    for i := range ch {
        fmt.Println("receive value: ", i)
    }

    fmt.Println("finish")
}

第二个演示,使用context

package main

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

func main() {
    forever := make(chan struct{})
    ctx, cancel := context.WithCancel(context.Background())

    go func(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():  // if cancel() execute
                forever <- struct{}{}
                return
            default:
                fmt.Println("for loop")
            }

            time.Sleep(500 * time.Millisecond)
        }
    }(ctx)

    go func() {
        time.Sleep(3 * time.Second)
        cancel()
    }()

    <-forever
    fmt.Println("finish")
}

我知道这个答案已经被接受了,但我想我会投入我的 2cents。我喜欢使用tomb包。 它基本上是一个 suped up 退出频道,但它也做一些不错的事情,比如回传任何错误。 受控制的例程仍负责检查远程终止信号。 Afaik 不可能获得 goroutine 的“id”并在它行为不端时将其杀死(即:陷入无限循环)。

这是我测试的一个简单示例:

package main

import (
  "launchpad.net/tomb"
  "time"
  "fmt"
)

type Proc struct {
  Tomb tomb.Tomb
}

func (proc *Proc) Exec() {
  defer proc.Tomb.Done() // Must call only once
  for {
    select {
    case <-proc.Tomb.Dying():
      return
    default:
      time.Sleep(300 * time.Millisecond)
      fmt.Println("Loop the loop")
    }
  }
}

func main() {
  proc := &Proc{}
  go proc.Exec()
  time.Sleep(1 * time.Second)
  proc.Tomb.Kill(fmt.Errorf("Death from above"))
  err := proc.Tomb.Wait() // Will return the error that killed the proc
  fmt.Println(err)
}

输出应如下所示:

# Loop the loop
# Loop the loop
# Loop the loop
# Loop the loop
# Death from above

就个人而言,我想在 goroutine 的通道上使用 range:

https://play.golang.org/p/qt48vvDu8cd

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    c := make(chan bool)
    wg.Add(1)
    go func() {
        defer wg.Done()
        for b := range c {
            fmt.Printf("Hello %t\n", b)
        }
    }()
    c <- true
    c <- true
    close(c)
    wg.Wait()
}

Dave 写了一篇关于这个的好文章: http : //dave.cheney.net/2013/04/30/curious-channels

我将提供一种与此处提供的方法略有不同的方法。

我将假设需要停止的goroutine正在执行一些与其他goroutines完全无关的工作。 该工作将由default select case

default:
    fmt.Println("working")
    time.Sleep(1 * time.Second)

另一个goroutine (在我的例子中是main )决定它应该停止正在执行一些工作的goroutine 你不能真正杀死goroutine 即使可以,这也是一个坏主意,因为它可能会使goroutine处于不希望的状态。 因此,我们必须使用通道来传达有人正在向goroutine发出停止信号。

stop := make(chan struct{})

由于goroutine将不断执行一些工作。 我们将使用一个循环来表示。 当停止信号发送时, goroutine跳出循环。

go func() {
L:
    for {
        select {
        case <-stop:
            fmt.Println("stopping")
            break L
        default:
            fmt.Println("working")
            time.Sleep(1 * time.Second)
        }
    }
}()

我们可以使用另一个通道向main指示 goroutine 已停止。 这是完整的示例:

package main

import (
    "fmt"
    "time"
)

func main() {
    stop := make(chan struct{})
    stopped := make(chan struct{})

    go func() {
    L:
        for {
            select {
            case <-stop:
                fmt.Println("stopping")
                break L
            default:
                fmt.Println("working")
                time.Sleep(1 * time.Second)
            }
        }

        fmt.Println("stopped")
        stopped <- struct{}{}
    }()

    <-time.After(5 * time.Second)
    stop <- struct{}{} // send a signal to stop
    close(stop)
    <-stopped // wait for stop
}

main线程产生一个goroutine来执行一些工作一段时间(在本例中为 5 秒)。 当时间到期时,它会向goroutine发送停止信号并等待它,直到goroutine完全停止。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM