The Go Programming Language
› http://golang.org/
› Go Playground
Go Projects
› Revel Web Framework
hkhk366

go 语言如何关闭正在运行的协程?谢谢

  •  
  •   hkhk366 · May 7, 2022 · 7706 views
    This topic created in 1650 days ago, the information mentioned may be changed or developed.
    package main
    
    import (
    	"fmt"
    	"sync"
    	"time"
    )
    
    func workRoutine(work chan string, done chan string, wg *sync.WaitGroup) {
    	select {
    	case donemsg := <-done:
    		fmt.Println(donemsg)
    	case msg := <-work:
    		//这里将有大量工作可能半个小时都未必执行完,这样 done 这个 channel 就无法收到退出信号
    		for i := 0; i < 100000; i++ {
    			fmt.Println(msg)
    			time.Sleep(time.Second)
    		}
    	case <-time.After(time.Second * 1000):
    		fmt.Println("timeout")
    	}
    	wg.Done()
    }
    func closeWorkRoutine(done chan string, wg *sync.WaitGroup) {
    	//目标是 10 秒后希望能关掉 test 这个协程
    	time.Sleep(time.Second * 10)
    	done <- "done"
    	wg.Done()
    }
    func main() {
    	done := make(chan string)
    	work := make(chan string, 1)
    	wg := sync.WaitGroup{}
    	wg.Add(2)
    	work <- "work"
    	go workRoutine(work, done, &wg)
    	go closeWorkRoutine(done, &wg)
    	wg.Wait()
    }
    

    请参考上面的代码,我现在有两个协程,一个叫 workRoutine ,另一个叫 closeWorkRoutine ,我的目标是希望 closeWorkRoutine 可以在 10 秒后可以关闭 workRoutine 的协程,但是上面这个代码是无法关闭的,因为 work 过于繁重,永远轮不到执行关闭的时候。请问有什么办法可以直接关闭协程,而无需介意当前协程的状态,最好能像线程那样有一个 ID 我可以直接在外部强制关掉,请问我应该如何做呢,谢谢。

    Supplement 1  ·  May 7, 2022
    其实那个 for 循环那么长只是一个例子,我现实中的应用是那个地方相当于一个死循环,因为计算量非常大,这样好像永远无法触发上下文切换,我正在阅读 context ,看看 context 是否已可以取消一个一直运行的 for 循环
    37 replies  •  2022-05-11 23:55:16 +08:00
    rophie123
    rophie123
    May 7, 2022
    1
    context
    gollwang
    gollwang
    May 7, 2022
    2
    楼上正解
    codefever
    codefever
    May 7, 2022
    3
    如果想中途 cancel 掉,可以使用 context.WithCancel
    rekulas
    rekulas
    May 7, 2022❤️3
    4
    严格来说是没办法的,只有发生上下文切换的时候你才有机会执行退出逻辑,如果协程阻塞在某个操作你没有办法去关闭
    阻塞了 context 什么的都没用
    py88pQ2hZ7PJw0v4
    py88pQ2hZ7PJw0v4
    May 7, 2022
    5
    关闭掉的,context 只是 channel 发一个通知
    mainjzb
    mainjzb
    May 7, 2022
    6
    没办法
    hejw19970413
    hejw19970413
    May 7, 2022
    7
    context 正解
    brader
    brader
    May 7, 2022
    8
    添加事件机制? work 里面的循环,每干完一轮活,就检查是否有退出事件?
    hejw19970413
    hejw19970413
    May 7, 2022
    9
    package main

    import (
    "context"
    "time"
    )

    var work = make(chan struct{})

    func workRoutine(ctx context.Context) {

    f1 := func(ctx context.Context) {
    select {
    case <-ctx.Done():
    default:

    }
    }
    for {
    select {
    case <-ctx.Done():
    break
    case <-work:
    // 任务分解 , 分别限时
    c1, _ := context.WithTimeout(ctx, 10*time.Second)
    f1(c1)
    }
    }
    }
    func main() {
    c, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
    defer cancel()
    go workRoutine(c)
    select {}
    }

    不知道这样能不能满足你的要求
    dushixiang
    dushixiang
    May 7, 2022
    10
    执行每一个子任务的时候加上时间限制,并且没执行完一次子任务就检查一下是否需要退出。
    shakaraka
    shakaraka
    May 7, 2022
    11
    func workRoutine(work chan string, done chan string, wg *sync.WaitGroup) {
    defer wg.Done()
    for {
    select {
    case donemsg := <-done:
    fmt.Println(donemsg)
    return
    case <-time.After(time.Second * 1000):
    fmt.Println("timeout")
    return
    case msg := <-work:
    fmt.Println(msg)
    time.Sleep(time.Second)
    fmt.Println("work done")
    }
    }
    }

    这样的?
    ilylx2008
    ilylx2008
    May 7, 2022
    12
    `
    for (i:=0;i<100000;i++){
    select {
    case donemsg := <-done:
    //stop
    default:
    //do something
    }
    }
    Jessun
    Jessun
    May 7, 2022❤️1
    13
    context 要看怎么用,我的建议是将 context 继续向下传递。

    "//这里将有大量工作可能半个小时都未必执行完,这样 done 这个 channel 就无法收到退出信号"
    这里如果是一个执行很长的代码,从外部你无法干掉的。将 context 继续传入这个函数,以及它的子函数,即 context 来控制整个调用链路上的超时。

    根据目前信息,就这个思路最简单了。
    NIYIKI
    NIYIKI
    May 7, 2022
    14
    关闭不了的
    zhangfuguan
    zhangfuguan
    May 7, 2022
    15
    ```go

    package main

    import (
    "log"
    "sync"
    "time"
    )

    var wg sync.WaitGroup

    func worker(quit <-chan int) {
    defer wg.Done()
    for {
    select {
    case <-quit:
    log.Printf("收到退出信号")
    return // 必须 return ,否则 goroutine 是不会结束的
    default:
    log.Println("loading...")
    time.Sleep(time.Second * 1)
    }
    }
    }

    func main() {
    quit := make(chan int) // 退出通道

    wg.Add(5)

    go worker(quit) // work 1
    go worker(quit) // work 2
    go worker(quit) // work 3
    go worker(quit) // work 4
    go Done(quit) // 结束所有任务

    wg.Wait()
    }

    func Done(ch chan int) {
    defer wg.Done()

    time.Sleep(time.Second * 10)
    close(ch)
    }

    ```

    这样吗?
    walleL
    walleL
    May 7, 2022
    16
    //这里将有大量工作可能半个小时都未必执行完,这样 done 这个 channel 就无法收到退出信号
    for i := 0; i < 100000; i++ {
    fmt.Println(msg)
    time.Sleep(time.Second)
    }
    ------
    只能将上面这段长时间执行的操作分解,并在步骤间判断是否需要退出。上面已经有朋友讲过了
    keepeye
    keepeye
    May 7, 2022❤️1
    17
    楼上说 context 的真的认真审题了吗...

    不管是自定义的 done 还是用 context 包,必须在某个位置读取信号,我没见过从外部强制销毁 goroutine 的办法
    fighterlyt
    fighterlyt
    May 7, 2022❤️6
    18
    context 只不过是简化了之前传入控制 channel 的方法,如果底层没有 等待+检查+执行的机制,开弓没有回头箭
    tianyou666shen
    tianyou666shen
    May 7, 2022
    19
    你这等于要在外部强制 kill -9 杀掉这个 goroutine 的效果?
    考虑通过退出主协程的方式强制让子协程被杀死这种方式吗.
    比方主协程开启 workRoutine 子协程以后,监听信号,当你输入信号时,主协会直接退出,同时子协程也被退出
    stevefan1999
    stevefan1999
    May 7, 2022 via Android
    20
    沒有辦法直接關閉 只能提醒可以關閉 直到協程提起接受關閉才可以 這裡涉及一部分操作系統線程排程問題很複雜
    stach
    stach
    May 7, 2022
    21
    应该是你的代码写的有点问题:

    ```
    package main

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

    func workRoutine(done chan string, wg *sync.WaitGroup) {
    work := func() <-chan string{
    c := make(chan string, 1)
    msg := "work"
    go func() {
    for i := 0; i < 100000; i++ {
    fmt.Println(msg)
    time.Sleep(time.Second)
    }
    }()
    c <- msg
    return c
    }
    select {
    case donemsg := <-done:
    fmt.Println(donemsg)
    case msg := <-work():
    fmt.Println(msg)
    case <-time.After(time.Second * 1000):
    fmt.Println("timeout")
    }
    wg.Done()
    }

    func closeWorkRoutine(done chan string, wg *sync.WaitGroup) {
    time.Sleep(time.Second * 10)
    done <- "done"
    wg.Done()
    }

    func main() {
    done := make(chan string, 1)
    wg := sync.WaitGroup{}
    wg.Add(2)
    go workRoutine(done, &wg)
    go closeWorkRoutine(done, &wg)
    wg.Wait()
    }
    ```
    stach
    stach
    May 7, 2022
    22
    实际上 goroutine 是不建议暴露 ID 和外部改变 goroutine 的行为的,所以你不能像线程那样去控制它。

    我基于你的代码,改动的这个例子,是把死循环的 work 逻辑移动到了一个单独的 goroutine 中,这样 select 对应的其他 case 可以触发。如果死循环 work 它确实无法退出,我们最终是通过 main 函数退出来终止它的运行的,也就是进程退出。
    rekulas
    rekulas
    May 7, 2022
    23
    如果你的核心计算无法插入中断 /判断代码,那没有任何办法实现你的需求只能想其他方法
    其实你的问题描述不准确,应该改为如何(从外部)停止一个协程,目前是不支持的,协程只能内部关闭无法外部中断
    考虑下子进程方向?反正你要强制关闭那直接 kill 也实现差不多的效果。。
    yeyypp92
    yeyypp92
    May 7, 2022
    24
    赞同楼上,每个任务开始时检查是否有退出事件
    george404
    george404
    May 7, 2022
    25
    我不记得是哪里看到说 go 的设计逻辑对于这个问题的回应是:

    用户需要自己处理好退出的机制,通过外面方式去强制结束一个运行的携程是不安全的。
    shanks02
    shanks02
    May 7, 2022
    26
    隔一段代码监测一下是否被设置了退出标志位。这是最简单的思路。重庆 IT-golang 群:186 8029 2450 一起学习,一起成长。
    ClarkAbe
    ClarkAbe
    May 7, 2022
    27
    楼上没一个认真看问题的.....

    只能单独封装然后 exec 运行子进程然后杀掉了,因为前几年刚写 Golang 的时候也想过,后面发现 golang 的设计就是要一层一层优雅退出....如果那部分代码不是自己能控制的就只能另外开进程然后 ipc 或者 args 传任务参数过去然后通过 ipc 或者 stdout 获取返回信息....如果想要中途停止 exec 有个 kill 函数,直接杀掉就行.....
    fenghuang
    fenghuang
    May 7, 2022
    28
    @stach #22 按您所说的实现可以直接简化成这样了

    ```
    func workRoutine(wg *sync.WaitGroup) {
    go func() {
    go func() {
    for i := 0; i < 100000; i++ {
    fmt.Println("do something")
    time.Sleep(time.Second)
    }
    }()
    }()
    wg.Done()
    }

    func closeWorkRoutine(wg *sync.WaitGroup) {
    time.Sleep(time.Second * 10)
    wg.Done()
    }

    func main() {
    wg := sync.WaitGroup{}
    wg.Add(2)
    go workRoutine(&wg)
    go closeWorkRoutine(&wg)
    wg.Wait()
    }
    ```
    jeffh
    jeffh
    May 7, 2022
    29
    没法关闭,只能给 goroutine 发信号,goroutine 自己返回
    lakehylia
    lakehylia
    May 7, 2022
    30
    如果你不检查退出条件,线程也没办法强杀的
    joetse
    joetse
    May 7, 2022
    31
    强制退出会导致中间状态 /污染, 需要写 recovery 的逻辑.
    你可以在每一个 loop 中判断 close, 或者用一个专门的 process 去做你的 work, 收到 close 的信号直接 os.exit

    不过还是建议用 hadoop 或者 spark 之流去做你的 work, 不要用 kill 的方式退出程序.
    bugfan
    bugfan
    May 8, 2022
    32
    楼主如果一定要在某个地方做非常耗时的操作,并且这个操作不太方便重新改逻辑嵌入 context 或者设置标识位去控制的话,建议直接把这部分逻辑摘出来,用 exec 族函数拉起来一个进程去执行它,然后在需要的时候 kill 掉它,让操作系统去干这个事情。
    RealYourDad
    RealYourDad
    May 8, 2022
    33
    @tianyou666shen go 里面协程不都是独立的吗,怎么还有主协程、子协程一说
    lanlanye
    lanlanye
    May 8, 2022
    34
    正常来说应该是通过层层传递 ctx 来分解任务,避免你说的 「 work 过于繁重,永远轮不到执行关闭的时候」,就像楼上说的那样。

    如果确实做不到的话,试试下面这种方式:

    https://paste.org.cn/WYbBsPUBWn
    tianyou666shen
    tianyou666shen
    May 9, 2022
    35
    @zaunist main goroutine 有点特殊的,可以参考这个
    https://learnku.com/articles/41728
    (3) go func () 调度流程
    ob
    ob
    May 10, 2022
    36
    @lanlanye 这域名有点厉害
    work 再繁重的那部分,应该也能再分解成更小的功能吧,理论上不管啥操作,拆解到最最最小的那个操作,就不可能耗时很久了吧,然后每个都判断下 context 是否退出。
    lanlanye
    lanlanye
    May 11, 2022
    37
    @ob 因为需要贴代码临时找了个支持的地方,域名与我无关……计算任务不一定能拆解,而且还要考虑可能是集成其他人的代码,这种情况大概还是会发生的。
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   2100 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 70ms · UTC 15:07 · PVG 23:07 · LAX 07:07 · JFK 10:07
    ♥ Do have faith in what you're doing.