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