
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 我可以直接在外部强制关掉,请问我应该如何做呢,谢谢。
1 rophie123 May 7, 2022 context |
2 gollwang May 7, 2022 楼上正解 |
3 codefever May 7, 2022 如果想中途 cancel 掉,可以使用 context.WithCancel |
4 rekulas May 7, 2022 严格来说是没办法的,只有发生上下文切换的时候你才有机会执行退出逻辑,如果协程阻塞在某个操作你没有办法去关闭 阻塞了 context 什么的都没用 |
5 py88pQ2hZ7PJw0v4 May 7, 2022 关闭掉的,context 只是 channel 发一个通知 |
6 mainjzb May 7, 2022 没办法 |
7 hejw19970413 May 7, 2022 context 正解 |
8 brader May 7, 2022 添加事件机制? work 里面的循环,每干完一轮活,就检查是否有退出事件? |
9 hejw19970413 May 7, 2022 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 {} } 不知道这样能不能满足你的要求 |
10 dushixiang May 7, 2022 执行每一个子任务的时候加上时间限制,并且没执行完一次子任务就检查一下是否需要退出。 |
11 shakaraka PRO 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") } } } 这样的? |
12 ilylx2008 May 7, 2022 ` for (i:=0;i<100000;i++){ select { case donemsg := <-done: //stop default: //do something } } |
13 Jessun May 7, 2022 context 要看怎么用,我的建议是将 context 继续向下传递。 "//这里将有大量工作可能半个小时都未必执行完,这样 done 这个 channel 就无法收到退出信号" 这里如果是一个执行很长的代码,从外部你无法干掉的。将 context 继续传入这个函数,以及它的子函数,即 context 来控制整个调用链路上的超时。 根据目前信息,就这个思路最简单了。 |
14 NIYIKI May 7, 2022 关闭不了的 |
15 zhangfuguan May 7, 2022 ```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) } ``` 这样吗? |
16 walleL May 7, 2022 //这里将有大量工作可能半个小时都未必执行完,这样 done 这个 channel 就无法收到退出信号 for i := 0; i < 100000; i++ { fmt.Println(msg) time.Sleep(time.Second) } ------ 只能将上面这段长时间执行的操作分解,并在步骤间判断是否需要退出。上面已经有朋友讲过了 |
17 keepeye May 7, 2022 楼上说 context 的真的认真审题了吗... 不管是自定义的 done 还是用 context 包,必须在某个位置读取信号,我没见过从外部强制销毁 goroutine 的办法 |
18 fighterlyt May 7, 2022 context 只不过是简化了之前传入控制 channel 的方法,如果底层没有 等待+检查+执行的机制,开弓没有回头箭 |
19 tianyou666shen May 7, 2022 你这等于要在外部强制 kill -9 杀掉这个 goroutine 的效果? 考虑通过退出主协程的方式强制让子协程被杀死这种方式吗. 比方主协程开启 workRoutine 子协程以后,监听信号,当你输入信号时,主协会直接退出,同时子协程也被退出 |
20 stevefan1999 May 7, 2022 via Android 有法直接 只能提醒可以 直到程提起接受才可以 涉及一部分操作系程排程很 |
21 stach May 7, 2022 应该是你的代码写的有点问题: ``` 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() } ``` |
22 stach May 7, 2022 实际上 goroutine 是不建议暴露 ID 和外部改变 goroutine 的行为的,所以你不能像线程那样去控制它。 我基于你的代码,改动的这个例子,是把死循环的 work 逻辑移动到了一个单独的 goroutine 中,这样 select 对应的其他 case 可以触发。如果死循环 work 它确实无法退出,我们最终是通过 main 函数退出来终止它的运行的,也就是进程退出。 |
23 rekulas May 7, 2022 如果你的核心计算无法插入中断 /判断代码,那没有任何办法实现你的需求只能想其他方法 其实你的问题描述不准确,应该改为如何(从外部)停止一个协程,目前是不支持的,协程只能内部关闭无法外部中断 考虑下子进程方向?反正你要强制关闭那直接 kill 也实现差不多的效果。。 |
24 yeyypp92 May 7, 2022 赞同楼上,每个任务开始时检查是否有退出事件 |
25 george404 May 7, 2022 我不记得是哪里看到说 go 的设计逻辑对于这个问题的回应是: 用户需要自己处理好退出的机制,通过外面方式去强制结束一个运行的携程是不安全的。 |
26 shanks02 May 7, 2022 隔一段代码监测一下是否被设置了退出标志位。这是最简单的思路。重庆 IT-golang 群:186 8029 2450 一起学习,一起成长。 |
27 ClarkAbe May 7, 2022 楼上没一个认真看问题的..... 只能单独封装然后 exec 运行子进程然后杀掉了,因为前几年刚写 Golang 的时候也想过,后面发现 golang 的设计就是要一层一层优雅退出....如果那部分代码不是自己能控制的就只能另外开进程然后 ipc 或者 args 传任务参数过去然后通过 ipc 或者 stdout 获取返回信息....如果想要中途停止 exec 有个 kill 函数,直接杀掉就行..... |
28 fenghuang May 7, 2022 @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() } ``` |
29 jeffh May 7, 2022 没法关闭,只能给 goroutine 发信号,goroutine 自己返回 |
30 lakehylia May 7, 2022 如果你不检查退出条件,线程也没办法强杀的 |
31 joetse May 7, 2022 强制退出会导致中间状态 /污染, 需要写 recovery 的逻辑. 你可以在每一个 loop 中判断 close, 或者用一个专门的 process 去做你的 work, 收到 close 的信号直接 os.exit 不过还是建议用 hadoop 或者 spark 之流去做你的 work, 不要用 kill 的方式退出程序. |
32 bugfan May 8, 2022 楼主如果一定要在某个地方做非常耗时的操作,并且这个操作不太方便重新改逻辑嵌入 context 或者设置标识位去控制的话,建议直接把这部分逻辑摘出来,用 exec 族函数拉起来一个进程去执行它,然后在需要的时候 kill 掉它,让操作系统去干这个事情。 |
33 RealYourDad May 8, 2022 @tianyou666shen go 里面协程不都是独立的吗,怎么还有主协程、子协程一说 |
34 lanlanye May 8, 2022 正常来说应该是通过层层传递 ctx 来分解任务,避免你说的 「 work 过于繁重,永远轮不到执行关闭的时候」,就像楼上说的那样。 如果确实做不到的话,试试下面这种方式: https://paste.org.cn/WYbBsPUBWn |
35 tianyou666shen May 9, 2022 |