我是一位golang新手,正在尝试了解此问题的正确设计模式。我当前的解决方案似乎很冗长,我不确定哪种更好的方法。
我正在尝试设计一个系统,该系统:
目标:我想开始一些goroutine,但是如果一个例程返回特定结果,我想取消这些例程。
我试图了解我的代码是否超级“臭”,或者这是否是规定的处理方式。我仍然感觉不太好,因此我们将不胜感激。
这是我写的:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
fooCheck := make(chan bool)
barCheck := make(chan bool)
go foo(ctx, 3000, fooCheck)
go bar(ctx, 5000, barCheck)
for fooCheck != nil ||
barCheck != nil {
select {
case res, ok := <-fooCheck:
if !ok {
fooCheck = nil
continue
}
if res == false {
cancel()
}
fmt.Printf("result of foocheck: %t\n", res)
case res, ok := <-barCheck:
if !ok {
barCheck = nil
continue
}
fmt.Printf("result of barcheck: %t\n", res)
}
}
fmt.Printf("here we are at the end of the loop, ready to do some more processing...")
}
func foo(ctx context.Context, pretendWorkTime int, in chan<- bool) {
fmt.Printf("simulate doing foo work and pass ctx down to cancel down the calltree\n")
time.Sleep(time.Millisecond * time.Duration(pretendWorkTime))
select {
case <-ctx.Done():
fmt.Printf("\n\nWe cancelled this operation!\n\n")
break
default:
fmt.Printf("we have done some foo work!\n")
in <- false
}
close(in)
}
func bar(ctx context.Context, pretendWorkTime int, in chan<- bool) {
fmt.Printf("simulate doing bar work and pass ctx down to cancel down the calltree\n")
time.Sleep(time.Millisecond * time.Duration(pretendWorkTime))
select {
case <-ctx.Done():
fmt.Printf("\n\nWe cancelled the bar operation!\n\n")
break
default:
fmt.Printf("we have done some bar work!\n")
in <- true
}
close(in)
}
Run Code Online (Sandbox Code Playgroud)
(在此处使用代码进行播放:https : //play.golang.org/p/HAA-LIxWNt0)
输出按预期方式工作,但恐怕我正在做出一些决定,这些决定以后会引起我的注意。
我会使用单个通道来传达结果,因此收集结果要容易得多,并且它会根据其性质自动“扩展”。如果您需要识别结果的来源,只需使用包含源的包装器即可。像这样的东西:
type Result struct {
ID string
Result bool
}
Run Code Online (Sandbox Code Playgroud)
为了模拟“真实”工作,工作人员应该使用循环以迭代方式完成工作,并且在每次迭代中他们应该检查取消信号。像这样的东西:
func foo(ctx context.Context, pretendWorkMs int, resch chan<- Result) {
log.Printf("foo started...")
for i := 0; i < pretendWorkMs; i++ {
time.Sleep(time.Millisecond)
select {
case <-ctx.Done():
log.Printf("foo terminated.")
return
default:
}
}
log.Printf("foo finished")
resch <- Result{ID: "foo", Result: false}
}
Run Code Online (Sandbox Code Playgroud)
在我们的示例中,bar()只需将所有foo单词替换为即可bar。
现在执行作业并在满足我们期望的情况下提前终止其余作业,如下所示:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
resch := make(chan Result, 2)
log.Println("Kicking off workers...")
go foo(ctx, 3000, resch)
go bar(ctx, 5000, resch)
for i := 0; i < cap(resch); i++ {
result := <-resch
log.Printf("Result of %s: %v", result.ID, result.Result)
if !result.Result {
cancel()
break
}
}
log.Println("Done.")
Run Code Online (Sandbox Code Playgroud)
运行此应用程序将输出(在Go Playground上尝试):
2009/11/10 23:00:00 Kicking off workers...
2009/11/10 23:00:00 bar started...
2009/11/10 23:00:00 foo started...
2009/11/10 23:00:03 foo finished
2009/11/10 23:00:03 Result of foo: false
2009/11/10 23:00:03 Done.
Run Code Online (Sandbox Code Playgroud)
有些事情需要注意。如果由于意外结果而提前终止,该cancel()函数将被调用,并且我们会跳出循环。其余的工作人员可能也会同时完成他们的工作并发送他们的结果,这不会成为问题,因为我们使用了缓冲通道,因此他们的发送不会阻塞并且他们将正确结束。另外,如果它们没有同时完成,它们会检查ctx.Done()循环,并提前终止,因此 goroutine 会被很好地清理。
另请注意,上述代码的输出不会打印bar terminated。这是因为main()函数在循环之后立即终止,并且一旦main()函数结束,它就不会等待其他非maingoroutine 完成。详细信息请参见Go 中 goroutine 无输出。如果应用程序不会立即终止,我们也会看到该行被打印出来。time.Sleep()如果我们在末尾添加一个main():
log.Println("Done.")
time.Sleep(3 * time.Millisecond)
Run Code Online (Sandbox Code Playgroud)
输出将是(在Go Playground上尝试):
2009/11/10 23:00:00 Kicking off workers...
2009/11/10 23:00:00 bar started...
2009/11/10 23:00:00 foo started...
2009/11/10 23:00:03 foo finished
2009/11/10 23:00:03 Result of foo: false
2009/11/10 23:00:03 Done.
2009/11/10 23:00:03 bar terminated.
Run Code Online (Sandbox Code Playgroud)
现在,如果您必须等待所有工作人员“正常”或“提前”结束才能继续,您可以通过多种方式实现这一点。
一种方法是使用sync.WaitGroup. 有关示例,请参阅在 Golang 中防止 main() 函数在 goroutine 完成之前终止。另一种方法是让每个工作人员发送 ,Result无论它们如何结束,并且Result可以包含终止条件,例如normalor aborted。Goroutinemain()可以继续接收循环,直到n从 接收到值resch。如果选择此解决方案,您必须确保每个工作人员发送一个值(即使发生恐慌),以免main()在这种情况下阻塞(例如使用 using defer)。
| 归档时间: |
|
| 查看次数: |
154 次 |
| 最近记录: |