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