V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
777777
V2EX  ›  Go 编程语言

如何终止一个正在进行的协程?

  •  
  •   777777 · 2021-08-30 15:17:45 +08:00 · 3562 次点击
    这是一个创建于 942 天前的主题,其中的信息可能已经有所发展或是发生改变。

    利用 context.WithCancel()来终止协程,但存在一个问题:cancel()执行后,必须等 for 循环执行完毕 goroutine 才退出。

    for {
      select {
        case <-Ctx.Done():
          return
        default:
          for i := 0; i < 100; i++ {
            // 业务逻辑
          }
      }
    }
    

    需求为:cancel()后协程立马退出不再执行后面的循环。
    目前我的解决方法:单独起一个协程用来监听退出信号,然后通过全局变量通知业务逻辑循环退出。

    flag := false
    go func() {
      for {
        select {
          case <-Ctx.Done():
            flag = true
            return
      }
    }()
    
    for i:=0; i < 100; i++ {
      if flag {
        return
      }
      // 业务逻辑
    }
    

    请问最佳实践应该是如何退出?

    第 1 条附言  ·  2021-08-30 15:54:34 +08:00
    是这样一个场景:用户创建任务,起一个 goroutine 去执行,执行过程中,用户想要取消这个任务。
    第 2 条附言  ·  2021-08-30 16:01:10 +08:00
    for i := 0; i < 100; i++ {
      select {
        case <-Ctx.Done():
        return
      default:
        // 业务逻辑
      }
    }
    

    感谢大家的回答,每次循环都检测一次退出信号就好了。
    延伸出另一个问题:用户取消任务,如何让default里的业务逻辑立马终止?

    第 3 条附言  ·  2021-08-30 16:21:23 +08:00

    看了一下os.exec.cmd.Start()的源码

    	if c.ctx != nil {
    		c.waitDone = make(chan struct{})
    		go func() {
    			select {
    			case <-c.ctx.Done():
    				c.Process.Kill()
    			case <-c.waitDone:
    			}
    		}()
    	}
    

    另起一个goroutine监听cancel(),然后kill掉执行的process。
    类比本问题,一个协程执行任务,一个协程监听任务的cancel(),任务取消直接kill掉执行任务的协程且监听协程也退出。

    21 条回复    2021-09-01 18:30:13 +08:00
    dream10201
        1
    dream10201  
       2021-08-30 15:24:09 +08:00
    瞎想的,你把 select case 放到 for 循环里面不行?
    dream10201
        2
    dream10201  
       2021-08-30 15:25:34 +08:00
    for {
    for i := 0; i < 100; i++ {
    select {
    case <-Ctx.Done():
    return
    default:
    // 业务逻辑
    }
    }
    }
    rimutuyuan
        3
    rimutuyuan  
       2021-08-30 15:26:40 +08:00
    ```
    for {
    for i := 0; i < 100; i++ {
    select {
    case <-Ctx.Done():
    return
    default:
    // 业务逻辑
    }
    }
    }```
    BBCCBB
        4
    BBCCBB  
       2021-08-30 15:26:59 +08:00
    这种可以在 for 里每次检测 ctx.Done()... 不过不是最佳实践.
    和你图中这个方法差不多,, 不过不知道你图中这个方法有没有变量可见性的问题? 导致 flag=true 后在当前协程并不可见?
    777777
        5
    777777  
    OP
       2021-08-30 15:50:04 +08:00
    @BBCCBB 是可见的
    777777
        6
    777777  
    OP
       2021-08-30 15:52:49 +08:00
    @dream10201 这样有问题吧,如果不调用 cancel(),最外面 for 循环不会退出。
    sadfQED2
        7
    sadfQED2  
       2021-08-30 16:06:43 +08:00 via Android
    这个问题我在 java 里面也研究过,最后结果就是无解,只能业务代码里面每次循环都判断
    xx6412223
        8
    xx6412223  
       2021-08-30 16:13:13 +08:00
    直接退出就不是合理的设计。
    caryqy
        9
    caryqy  
       2021-08-30 16:24:47 +08:00
    执行前检查 Done,

    执行后提交前再次检查 Done,根据结果来收尾。

    考虑的粒度即使再细都会碰到 执行中取消 这个问题,所以到某个程度之后就 禁止用户取消
    haochen2
        10
    haochen2  
       2021-08-30 17:11:31 +08:00
    如果业务逻辑超时运行或着阻塞,根本走不到 ctx.Done 那个 case 的地方
    cpstar
        11
    cpstar  
       2021-08-30 17:34:57 +08:00
    循环第二位置用 i<100&&ctx.done() 不好么?还非得一个 switch-case ?
    至于楼上说的业务阻塞,那就得放到业务里边继续判断了。

    多线程运转,直接干掉线程,哈
    MidGap
        12
    MidGap  
       2021-08-30 17:46:36 +08:00
    runtime.Goexit()
    fpure
        13
    fpure  
       2021-08-30 18:17:32 +08:00
    @sadfQED2 Java 里面哪有什么协程?
    sadfQED2
        14
    sadfQED2  
       2021-08-30 18:17:50 +08:00 via Android
    @fpure java 线程
    s4nd
        15
    s4nd  
       2021-08-30 18:57:42 +08:00
    老哥,你这头像是认真的吗,nft 大佬
    MineDog
        16
    MineDog  
       2021-08-30 20:19:39 +08:00
    @sadfQED2 #7 java 怎么会无解呢,java 一般就是 Interrupted+状态判断啊
    cheng6563
        17
    cheng6563  
       2021-08-31 09:15:33 +08:00
    @sadfQED2 Java 一顿 Interrupt 就行了,只不过没人管 InterruptedException 倒是真的
    codeface
        18
    codeface  
       2021-08-31 13:05:14 +08:00
    caoyouming
        19
    caoyouming  
       2021-09-01 12:09:53 +08:00
    你这里的 c 表示的是什么呀?
    ltf127001
        20
    ltf127001  
       2021-09-01 17:55:42 +08:00
    @caoyouming 根据我在菜鸟教程学了两天 go 的经验,这个应该是个通道( channel )
    caoyouming
        21
    caoyouming  
       2021-09-01 18:30:13 +08:00
    @ltf127001 #20 if c.ctx != nil {
    c.waitDone = make(chan struct{})
    go func() {
    select {
    case <-c.ctx.Done():
    c.Process.Kill()
    case <-c.waitDone:
    }
    }()
    }

    这里的 c 应该不是 channel 吧?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2840 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 12:57 · PVG 20:57 · LAX 05:57 · JFK 08:57
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.