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

2022-05-07 11:24:29 +08:00
 hkhk366
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 我可以直接在外部强制关掉,请问我应该如何做呢,谢谢。

5832 次点击
所在节点    Go 编程语言
37 条回复
stach
2022-05-07 15:52:15 +08:00
应该是你的代码写的有点问题:

```
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
2022-05-07 15:59:44 +08:00
实际上 goroutine 是不建议暴露 ID 和外部改变 goroutine 的行为的,所以你不能像线程那样去控制它。

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

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

只能单独封装然后 exec 运行子进程然后杀掉了,因为前几年刚写 Golang 的时候也想过,后面发现 golang 的设计就是要一层一层优雅退出....如果那部分代码不是自己能控制的就只能另外开进程然后 ipc 或者 args 传任务参数过去然后通过 ipc 或者 stdout 获取返回信息....如果想要中途停止 exec 有个 kill 函数,直接杀掉就行.....
fenghuang
2022-05-07 19:24:17 +08:00
@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
2022-05-07 19:26:03 +08:00
没法关闭,只能给 goroutine 发信号,goroutine 自己返回
lakehylia
2022-05-07 19:38:54 +08:00
如果你不检查退出条件,线程也没办法强杀的
joetse
2022-05-07 21:07:17 +08:00
强制退出会导致中间状态 /污染, 需要写 recovery 的逻辑.
你可以在每一个 loop 中判断 close, 或者用一个专门的 process 去做你的 work, 收到 close 的信号直接 os.exit

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

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

https://paste.org.cn/WYbBsPUBWn
tianyou666shen
2022-05-09 09:53:44 +08:00
@zaunist main goroutine 有点特殊的,可以参考这个
https://learnku.com/articles/41728
(3) go func () 调度流程
ob
2022-05-10 20:34:17 +08:00
@lanlanye 这域名有点厉害
work 再繁重的那部分,应该也能再分解成更小的功能吧,理论上不管啥操作,拆解到最最最小的那个操作,就不可能耗时很久了吧,然后每个都判断下 context 是否退出。
lanlanye
2022-05-11 23:55:16 +08:00
@ob 因为需要贴代码临时找了个支持的地方,域名与我无关……计算任务不一定能拆解,而且还要考虑可能是集成其他人的代码,这种情况大概还是会发生的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/851321

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX