Go 在同一执行线程中没有通道的上下文
如果通过上下文语义在同一个执行线程中计算需要花费大量时间,我无法理解如何取消任务 我使用这个例子作为参考点 这里的目标是调用一个doWork,如果doWork需要很多时间来计算,GetValueWithDeadline应该在超时后返回0,或者如果调用方调用cancel来取消等待(这里是主叫方),或者在给定时间窗口中返回值 同样的场景可以用不同的方式完成。(单独的goroutine sleep、wakeup check值等、互斥条件等)但我真的很想了解使用上下文的正确方法 通道语义我理解,但在这里我无法达到预期效果,默认情况下 在默认情况下调用doWork故障并休眠Go 在同一执行线程中没有通道的上下文,go,concurrency,Go,Concurrency,如果通过上下文语义在同一个执行线程中计算需要花费大量时间,我无法理解如何取消任务 我使用这个例子作为参考点 这里的目标是调用一个doWork,如果doWork需要很多时间来计算,GetValueWithDeadline应该在超时后返回0,或者如果调用方调用cancel来取消等待(这里是主叫方),或者在给定时间窗口中返回值 同样的场景可以用不同的方式完成。(单独的goroutine sleep、wakeup check值等、互斥条件等)但我真的很想了解使用上下文的正确方法 通道语义我理解,但在这
package main
import (
"context"
"fmt"
"log"
"math/rand"
"sync"
"time"
)
type Server struct {
lock sync.Mutex
}
func NewServer() *Server {
s := new(Server)
return s
}
func (s *Server) doWork() int {
s.lock.Lock()
defer s.lock.Unlock()
r := rand.Intn(100)
log.Printf("Going to nap for %d", r)
time.Sleep(time.Duration(r) * time.Millisecond)
return r
}
// I take an example from here and it very unclear where is do work executed
// https://golang.org/src/context/context_test.go
func (s *Server) GetValueWithDeadline(ctx context.Context) int {
val := 0
select {
case <- time.After(150 * time.Millisecond):
fmt.Println("overslept")
return 0
case <- ctx.Done():
fmt.Println(ctx.Err())
return 0
default:
val = s.doWork()
}
return all
}
func main() {
rand.Seed(time.Now().UTC().UnixNano())
s := NewServer()
for i :=0; i < 10; i++ {
d := time.Now().Add(50 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
log.Print(s.GetValueWithDeadline(ctx))
cancel()
}
}
主程序包
进口(
“上下文”
“fmt”
“日志”
“数学/兰德”
“同步”
“时间”
)
类型服务器结构{
锁同步
}
func NewServer()*服务器{
s:=新(服务器)
返回s
}
func(s*服务器)doWork()int{
s、 lock.lock()
延迟s.lock.Unlock()
r:=兰特整数(100)
log.Printf(“要为%d打瞌睡”,r)
时间.睡眠(时间.持续时间(r)*时间.毫秒)
返回r
}
//我举了一个例子,很不清楚在哪里执行工作
// https://golang.org/src/context/context_test.go
func(s*服务器)GetValueWithDeadline(ctx context.context)int{
val:=0
挑选{
案例您的方法存在多个问题
语境能解决什么问题
首先,在Go中创建上下文的主要原因是它们允许统一一种方法来取消一组任务
使用一个简单的例子来解释这个概念,考虑到某个服务器的客户端请求;以简化它进一步让它成为HTTP请求。
客户端连接到服务器,发送一些数据告诉服务器如何满足请求,然后等待服务器响应。
现在让我们假设请求需要在服务器上进行复杂而耗时的处理—例如,假设它需要对多个远程数据库引擎执行多个复杂查询,对外部服务执行多个HTTP请求,然后处理获取的结果以实际生成客户机需要的数据
因此,客户端启动其请求,服务器继续执行所有这些请求。
为了隐藏服务器为完成请求而必须执行的单个任务的延迟,它在单独的goroutine中运行这些任务。
一旦每个goroutine完成分配的任务,它就会将其结果(和/或错误)传回处理客户端请求的goroutine,以此类推
现在假设客户端由于任何原因无法等待对其请求的响应—网络中断、客户端软件中的显式超时、用户杀死启动请求的应用程序等—有很多可能性
正如您所看到的,服务器继续花费资源来完成逻辑上绑定到现在已失效的请求的任务没有什么意义:反正没有人会听到结果。
因此,一旦我们知道请求将无法完成,就可以收获这些任务,而这正是上下文发挥作用的地方:您可以将每个传入请求与单个上下文关联,然后将其自身传递给生成的任何goroutine,以执行完成请求所需的单个任务,或者派生另一个请求从这一点,并通过它代替。
然后,一旦取消“根”请求,该信号将通过从根请求派生的整个请求树传播。
现在,每个被赋予了上下文的goroutine都可能“监听”它,以便在发送取消信号时得到通知,一旦goroutine注意到它可能会放弃它正在做的任何事情并退出
根据实际的context.context
输入该信号称为“完成”-如“我们完成了与该上下文相关的任何操作”-这就是为什么goroutine希望知道它应该停止工作,并在名为done
的上下文方法返回的特殊通道上侦听
回到你的例子
要使其工作,您可以执行以下操作:
func(s*Server)doWork(ctx context.context)int{
s、 lock.lock()
延迟s.lock.Unlock()
r:=兰特整数(100)
log.Printf(“要为%d打瞌睡”,r)
挑选{
案例非常感谢您给出了非常清晰的解释。这对我来说很有意义。我还评估了doWork返回通道的第二个选项。即缓冲通道,当它完成时关闭通道。在我的案例中,缩小到小示例,但如果doWork等待互斥API,则不会返回任何结果,因此它会尝试获取值并关闭通道如果不能阻止,则不要阻止。Go具有非常丰富的并发语义;)