有没有办法停止一个长时间阻塞的函数?

huangapple go评论149阅读模式
英文:

Is there a way to stop a long blocking function?

问题

我有一个运行几分钟的函数,我正在尝试找到一种使用通道停止它的方法。

我认为我不能像下面的代码那样做,因为我认为select只会在default完成后处理stop的情况。

package main

import (
	"fmt"
	"time"
)

func main() {
	stop := make(chan int)

	go func() {
		for {
			select {
			case <-stop:
				fmt.Println("return")
				return
			default:
				fmt.Println("block")
				time.Sleep(5 * time.Second) // 模拟一个长时间运行的函数
				fmt.Println("unblock")
			}
		}
	}()

	time.Sleep(1 * time.Second)
	stop <- 1
}
英文:

I have a function that run for minutes and I'm trying to find a way to stop it using a channel.

I think I can't do it like I do in the following code since I think the select will only handle the stop case after the default is done.

<!-- language: lang-golang -->

package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

func main() {
	stop := make(chan int)

	go func() {
		for {
			select {
			case &lt;-stop:
				fmt.Println(&quot;return&quot;)
				return
			default:
				fmt.Println(&quot;block&quot;)
				time.Sleep(5 * time.Second) // simulate a long running function
				fmt.Println(&quot;unblock&quot;)
			}
		}
	}()

	time.Sleep(1 * time.Second)
	stop &lt;- 1
}

答案1

得分: 10

我认为你无法做到这一点:在Go语言中,goroutine在某种意义上是协作的:除非一个goroutine主动尝试弄清楚是否应该退出,否则没有办法强制它这样做。

实际上,我认为这是一种特性,因为如果你可以强制终止一个长时间运行的goroutine,你将无法确保它是否干净地退出,也就是说,是否正确释放了所有已获取的资源。

所以要么接受这一点(比如,如果你的进程想要退出,只需等待该goroutine完成),要么重新设计它,使其定期检查是否被要求退出。甚至可以考虑将其执行的任务转移到外部进程中(但请注意,虽然可以安全地终止一个进程以释放其从操作系统获取的资源,但对于该进程可能正在更新的外部数据来说是不安全的,比如文件)。

英文:

I reckon you can't do it: goroutines in Go are, in a sense, cooperative: until a goroutine actively tries to somehow figure out if it should exit, there's no way to force it to do so.

I'd say it's a feature in fact because if you could forcibly reap a long-running goroutine, you would be unable to be sure it exited cleanly, that is, properly released all resources it had acquired.

So either live with this (say, if your process wants to exit, just wait on that goroutine to finish) or restructure it so that it periodically checks whether it is signaled to quit. Or even consider offloading the task it performs to an external process (but note that while it's safe to kill a process with regard to releasing the resources it acquired from the OS, it's not safe with regard to external data that process might have been updating &mdash; such as files).

答案2

得分: 6

我不认为你可以结束一个 goroutine,但你可以切换到另一个 goroutine。
你可以通过将函数包装在一个发送数据到通道的 goroutine 中来设置超时。然后需要使用 select 等待返回的通道或超时通道。

示例代码如下:

package main

import (
    "fmt"
    "time"
)

func waitForMe() {
    time.Sleep(time.Second * 5)
}

func main() {
    c1 := make(chan string, 1)
    go func() {
        waitForMe()
        c1 <- "waitForMe is done"
    }()
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(time.Second * 2):
        fmt.Println("timed out")
    }
}

请注意,每次调用 selecttime.After() 或有阻塞的通道时,如果可能的话,goroutine 会切换到下一个可用的 goroutine。

以下是你的程序的运行情况:

package main

import (
    "fmt"
    "time"
)

func main() {
    stop := make(chan int)
    go func() {
        fmt.Println("Goroutine B before for loop")
        for {
            fmt.Println("Goroutine B inside for loop")
            select {
            case <-stop:
                fmt.Println("return")
                return
            default:
                fmt.Println("Goroutine B default case")
                fmt.Println("block")
                time.Sleep(5 * time.Second) // 模拟一个长时间运行的函数
                fmt.Println("unblock")
            }
        }
    }()

    fmt.Println("Goroutine A before time.Sleep()")
    time.Sleep(1 * time.Second)
    fmt.Println("Goroutine A after sleep")
    stop <- 1
    fmt.Println("Goroutine A after stop")
}

输出结果为:

Goroutine A before time.Sleep()
Goroutine B before for loop
Goroutine B inside for loop
Goroutine B default case
block
Goroutine A after sleep
unblock
Goroutine B inside for loop
return
Goroutine A after stop

有用的链接:
https://gobyexample.com/timeouts

英文:

I don't think you can end a goroutine but you can switch to a different one.
You can timeout a function by wrapping it inside a goroutine that send data to a channel once complete. There then needs to be select waiting for the returned channel or the channel from a timeout.

Example:

package main

import (
    &quot;fmt&quot;
    &quot;time&quot;
)

func waitForMe(){
        time.Sleep(time.Second*5)
}
func main(){
        c1 := make(chan string, 1)
        go func(){
                waitForMe()
                c1 &lt;- &quot;waitForMe is done&quot;
        }()
        select {
        case res := &lt;-c1:
                fmt.Println(res)
        case &lt;-time.After(time.Second*2):
                fmt.Println(&quot;timed out&quot;)
        }
}

Note every time you call select, time.After() or have a blocking channel, the goroutines switches to next available goroutine if possible.

Here's what's happening with your program.

Your program with Goroutine statements

package main

import (
    &quot;fmt&quot;
    &quot;time&quot;
)

func main() {
    stop := make(chan int)
    go func() {
       fmt.Println(&quot;Goroutine B before for loop&quot;)
        for {
            fmt.Println(&quot;Goroutine B inside for loop&quot;)
            select {
            case &lt;-stop:
                fmt.Println(&quot;return&quot;)
                return
            default:
                fmt.Println(&quot;Goroutine B default case&quot;)
                fmt.Println(&quot;block&quot;)
                time.Sleep(5 * time.Second) // simulate a long running function
                fmt.Println(&quot;unblock&quot;)
            }
        }
    }()

    fmt.Println(&quot;Goroutine A before time.Sleep()&quot;)
    time.Sleep(1 * time.Second)
    fmt.Println(&quot;Goroutine A after sleep&quot;)
    stop &lt;- 1
    fmt.Println(&quot;Goroutine A after stop&quot;)
}

Output

Goroutine A before time.Sleep()
Goroutine B before for loop
Goroutine B inside for loop
Goroutine B default case
block
Goroutine A after sleep
unblock
Goroutine B inside for loop
return
Goroutine A after stop

Useful link:
https://gobyexample.com/timeouts

答案3

得分: 2

你只能在某种程度上使你的函数可中断,也就是说,你不能单独停止一个阻塞调用。如果你自己编写阻塞函数,通常可以使用多个 case 和通道来创建一个 select。

你的示例代码如下:

package main

import (
	"fmt"
	"time"
)

func main() {
	stop := make(chan int)

	go func() {
		for {
			fmt.Println("block")
			select {
			case <-time.After(5 * time.Second):
				fmt.Println("unblock")
			case <-stop:
				fmt.Println("stopped")
				return
			}

		}
	}()

	time.Sleep(2 * time.Second)
	stop <- 1

	// 这只是为了让 goroutine 在我们退出之前有足够的时间写入 "stopped"
	select{}
}

希望对你有帮助!

英文:

You can only do this if you somehow make your function interruptible, i.e. you can't stop a blocking call on it's own. If you're writing the blocking function yourself, you can usually fashion a select with multiple cases and channels.

Your example would look like

package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

func main() {
	stop := make(chan int)

	go func() {
		for {
			fmt.Println(&quot;block&quot;)
			select {
			case &lt;-time.After(5 * time.Second):
				fmt.Println(&quot;unblock&quot;)
			case &lt;-stop:
				fmt.Println(&quot;stopped&quot;)
				return
			}

		}
	}()

	time.Sleep(2 * time.Second)
	stop &lt;- 1

	// this is just to give the goroutine time to write &quot;stopped&quot; before we exit
	select{}
}

huangapple
  • 本文由 发表于 2013年12月7日 01:00:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/20429489.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定