是否可以取消未完成的 goroutines?
Is it possible to cancel unfinished goroutines?
考虑一组check作品,每个作品都有独立的逻辑,所以它们似乎对运行并发很好,比如:
type Work struct {
// ...
}
// This Check could be quite time-consuming
func (w *Work) Check() bool {
// return succeed or not
//...
}
func CheckAll(works []*Work) {
num := len(works)
results := make(chan bool, num)
for _, w := range works {
go func(w *Work) {
results <- w.Check()
}(w)
}
for i := 0; i < num; i++ {
if r := <-results; !r {
ReportFailed()
break;
}
}
}
func ReportFailed() {
// ...
}
当关注results
时,如果逻辑是无论哪一个工作失败,我们断言所有工作完全失败,通道中的剩余值是无用。让剩余未完成的 goroutines 继续 运行 并将结果发送到通道是没有意义和浪费的,尤其是当 w.Check()
相当耗时时。理想效果类似于:
for _, w := range works {
if !w.Check() {
ReportFailed()
break;
}
}
这只是 运行 必要的检查工作然后中断,但在顺序非并发情况下。
那么,是否可以取消这些未完成的goroutine,或者发送到频道?
正在取消(阻塞)发送
您原来的问题是问如何取消发送操作。频道上的发送基本上是“即时的”。如果通道的缓冲区已满并且没有准备好的接收器,则通道上的发送将阻塞。
您 可以 使用 select
语句和您关闭的 cancel
频道“取消”此发送,例如:
cancel := make(chan struct{})
select {
case ch <- value:
case <- cancel:
}
在另一个 goroutine 上使用 close(cancel)
关闭 cancel
通道将使上面的 select 放弃 ch
上的发送(如果它正在阻塞)。
但是如前所述,发送在“就绪”通道上是“即时的”,并且发送首先评估要发送的值:
results <- w.Check()
这首先必须 运行 w.Check()
,完成后,其 return 值将在 results
.
上发送
正在取消函数调用
所以你真正需要的是取消w.Check()
方法调用。为此,惯用的方法是传递一个可以取消的 context.Context
值,并且 w.Check()
本身必须监视并“服从”这个取消请求。
见
请注意,您的函数必须明确支持这一点。没有函数调用或 goroutines 的隐式终止,参见 。
所以你的 Check()
应该看起来像这样:
// This Check could be quite time-consuming
func (w *Work) Check(ctx context.Context, workDuration time.Duration) bool {
// Do your thing and monitor the context!
select {
case <-ctx.Done():
return false
case <-time.After(workDuration): // Simulate work
return true
case <-time.After(2500 * time.Millisecond): // Simulate failure after 2.5 sec
return false
}
}
而 CheckAll()
可能看起来像这样:
func CheckAll(works []*Work) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
num := len(works)
results := make(chan bool, num)
wg := &sync.WaitGroup{}
for i, w := range works {
workDuration := time.Second * time.Duration(i)
wg.Add(1)
go func(w *Work) {
defer wg.Done()
result := w.Check(ctx, workDuration)
// You may check and return if context is cancelled
// so result is surely not sent, I omitted it here.
select {
case results <- result:
case <-ctx.Done():
return
}
}(w)
}
go func() {
wg.Wait()
close(results) // This allows the for range over results to terminate
}()
for result := range results {
fmt.Println("Result:", result)
if !result {
cancel()
break
}
}
}
正在测试:
CheckAll(make([]*Work, 10))
输出(在 Go Playground 上尝试):
Result: true
Result: true
Result: true
Result: false
我们 true
打印了 3 次(工作在 2.5 秒内完成),然后故障模拟开始,returns false
,并终止所有其他工作。
请注意,上面示例中的 sync.WaitGroup
并不是严格需要的,因为 results
有一个能够保存所有结果的缓冲区,但总的来说这仍然是一个好习惯(如果你使用较小的缓冲区将来)。
查看相关内容:
package main
import "fmt"
type Work struct {
// ...
Name string
IsSuccess chan bool
}
// This Check could be quite time-consuming
func (w *Work) Check() {
// return succeed or not
//...
if len(w.Name) > 0 {
w.IsSuccess <- true
}else{
w.IsSuccess <- false
}
}
//堆排序
func main() {
works := make([]*Work,3)
works[0] = &Work{
Name: "",
IsSuccess: make(chan bool),
}
works[1] = &Work{
Name: "111",
IsSuccess: make(chan bool),
}
works[2] =&Work{
Name: "",
IsSuccess: make(chan bool),
}
for _,w := range works {
go w.Check()
}
for i,w := range works{
select {
case checkResult := <-w.IsSuccess :
fmt.Printf("index %d checkresult %t \n",i,checkResult)
}
}
}
enter image description here
简短的回答是:否。
您不能取消或关闭任何 goroutine,除非 goroutine 本身到达 return
或其堆栈的末尾。
如果你想取消某些东西,最好的方法是传递一个 context.Context
给他们,然后在例程中听这个 context.Done()
。每当上下文被取消时,你应该 return
并且 goroutine 将在执行延迟(如果有)后自动死亡。
考虑一组check作品,每个作品都有独立的逻辑,所以它们似乎对运行并发很好,比如:
type Work struct {
// ...
}
// This Check could be quite time-consuming
func (w *Work) Check() bool {
// return succeed or not
//...
}
func CheckAll(works []*Work) {
num := len(works)
results := make(chan bool, num)
for _, w := range works {
go func(w *Work) {
results <- w.Check()
}(w)
}
for i := 0; i < num; i++ {
if r := <-results; !r {
ReportFailed()
break;
}
}
}
func ReportFailed() {
// ...
}
当关注results
时,如果逻辑是无论哪一个工作失败,我们断言所有工作完全失败,通道中的剩余值是无用。让剩余未完成的 goroutines 继续 运行 并将结果发送到通道是没有意义和浪费的,尤其是当 w.Check()
相当耗时时。理想效果类似于:
for _, w := range works {
if !w.Check() {
ReportFailed()
break;
}
}
这只是 运行 必要的检查工作然后中断,但在顺序非并发情况下。
那么,是否可以取消这些未完成的goroutine,或者发送到频道?
正在取消(阻塞)发送
您原来的问题是问如何取消发送操作。频道上的发送基本上是“即时的”。如果通道的缓冲区已满并且没有准备好的接收器,则通道上的发送将阻塞。
您 可以 使用 select
语句和您关闭的 cancel
频道“取消”此发送,例如:
cancel := make(chan struct{})
select {
case ch <- value:
case <- cancel:
}
在另一个 goroutine 上使用 close(cancel)
关闭 cancel
通道将使上面的 select 放弃 ch
上的发送(如果它正在阻塞)。
但是如前所述,发送在“就绪”通道上是“即时的”,并且发送首先评估要发送的值:
results <- w.Check()
这首先必须 运行 w.Check()
,完成后,其 return 值将在 results
.
正在取消函数调用
所以你真正需要的是取消w.Check()
方法调用。为此,惯用的方法是传递一个可以取消的 context.Context
值,并且 w.Check()
本身必须监视并“服从”这个取消请求。
见
请注意,您的函数必须明确支持这一点。没有函数调用或 goroutines 的隐式终止,参见
所以你的 Check()
应该看起来像这样:
// This Check could be quite time-consuming
func (w *Work) Check(ctx context.Context, workDuration time.Duration) bool {
// Do your thing and monitor the context!
select {
case <-ctx.Done():
return false
case <-time.After(workDuration): // Simulate work
return true
case <-time.After(2500 * time.Millisecond): // Simulate failure after 2.5 sec
return false
}
}
而 CheckAll()
可能看起来像这样:
func CheckAll(works []*Work) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
num := len(works)
results := make(chan bool, num)
wg := &sync.WaitGroup{}
for i, w := range works {
workDuration := time.Second * time.Duration(i)
wg.Add(1)
go func(w *Work) {
defer wg.Done()
result := w.Check(ctx, workDuration)
// You may check and return if context is cancelled
// so result is surely not sent, I omitted it here.
select {
case results <- result:
case <-ctx.Done():
return
}
}(w)
}
go func() {
wg.Wait()
close(results) // This allows the for range over results to terminate
}()
for result := range results {
fmt.Println("Result:", result)
if !result {
cancel()
break
}
}
}
正在测试:
CheckAll(make([]*Work, 10))
输出(在 Go Playground 上尝试):
Result: true
Result: true
Result: true
Result: false
我们 true
打印了 3 次(工作在 2.5 秒内完成),然后故障模拟开始,returns false
,并终止所有其他工作。
请注意,上面示例中的 sync.WaitGroup
并不是严格需要的,因为 results
有一个能够保存所有结果的缓冲区,但总的来说这仍然是一个好习惯(如果你使用较小的缓冲区将来)。
查看相关内容:
package main
import "fmt"
type Work struct {
// ...
Name string
IsSuccess chan bool
}
// This Check could be quite time-consuming
func (w *Work) Check() {
// return succeed or not
//...
if len(w.Name) > 0 {
w.IsSuccess <- true
}else{
w.IsSuccess <- false
}
}
//堆排序
func main() {
works := make([]*Work,3)
works[0] = &Work{
Name: "",
IsSuccess: make(chan bool),
}
works[1] = &Work{
Name: "111",
IsSuccess: make(chan bool),
}
works[2] =&Work{
Name: "",
IsSuccess: make(chan bool),
}
for _,w := range works {
go w.Check()
}
for i,w := range works{
select {
case checkResult := <-w.IsSuccess :
fmt.Printf("index %d checkresult %t \n",i,checkResult)
}
}
}
enter image description here
简短的回答是:否。
您不能取消或关闭任何 goroutine,除非 goroutine 本身到达 return
或其堆栈的末尾。
如果你想取消某些东西,最好的方法是传递一个 context.Context
给他们,然后在例程中听这个 context.Done()
。每当上下文被取消时,你应该 return
并且 goroutine 将在执行延迟(如果有)后自动死亡。