关于网站维护的书籍,打不开建设银行网站,国外优秀电商设计网站,货代一般都去哪个网站找客户简介
今天#xff0c;我们将讨论 Go 编程中非常重要的一个主题#xff1a;context 包。如果你现在觉得它很令人困惑#xff0c;不用担心 — 在本文结束时#xff0c;你将像专家一样处理 context#xff01;
想象一下#xff0c;你在一个主题公园#xff0c;兴奋地准备…
简介
今天我们将讨论 Go 编程中非常重要的一个主题context 包。如果你现在觉得它很令人困惑不用担心 — 在本文结束时你将像专家一样处理 context
想象一下你在一个主题公园兴奋地准备搭乘一座巨大的过山车。但有个问题排队的人非常多而且公园快要关门你只有一个小时的时间。你会怎么办嗯你可能会等一会儿但不会等一个小时对吧如果你等了 30 分钟还没有到前面你会离开队伍去尝试其他游乐设施。这就是我们所谓的 ‘超时’。
现在想象一下你还在排队突然下起了倾盆大雨。过山车的操作员决定关闭过山车。你不会继续排队等待根本不会发生的事情对吧你会立刻离开队伍。这就是我们所谓的 ‘取消’。
在编程世界中我们经常面临类似的情况。我们要求程序执行可能需要很长时间或需要因某种原因停止的任务。这就是 context 包发挥作用的地方。它允许我们优雅地处理这些超时和取消。
它是如何工作的
**创建上下文**我们首先创建一个上下文。这就像排队等待过山车一样。
ctx : context.Background() // This gives you an empty context**设置超时**接下来我们可以在上下文中设置超时。这就好比你决定在排队多久后放弃并去尝试其他游乐设施。
ctxWithTimeout, cancel : context.WithTimeout(ctx, time.Second*10) // Wait for 10 seconds
// Dont forget to call cancel when youre done, or else you might leak resources!
defer cancel()**检查超时**现在我们可以使用上下文来检查是否等待时间太长是否应该停止我们的任务。这就好比在排队等待时看看手表。
select {
case -time.After(time.Second * 15): // This task takes 15 secondsfmt.Println(Finished the task)
case -ctxWithTimeout.Done():fmt.Println(Weve waited too long, lets move on!) // We only wait for 10 seconds
}**取消上下文**最后如果出于某种原因需要停止任务我们可以取消上下文。这就好比听到因下雨而宣布过山车关闭。
cancel() // We call the cancel function we got when we created our context with timeout示例 1慢速数据库查询
想象一下构建一个从数据库中获取用户数据的Web应用程序。有时数据库响应较慢你不希望用户永远等下去。在这种情况下你可以使用带有超时的上下文。
func getUser(ctx context.Context, id int) (*User, error) {// Create a new context that will be cancelled if it takes more than 3 secondsctx, cancel : context.WithTimeout(ctx, 3*time.Second)defer cancel()// Assume db.QueryRowContext is a function that executes a SQL query and returns a rowrow : db.QueryRowContext(ctx, SELECT name FROM users WHERE id ?, id)var name stringif err : row.Scan(name); err ! nil {return nil, err}return User{Name: name}, nil
}在这个示例中如果数据库查询花费超过3秒的时间上下文将被取消db.QueryRowContext 应返回一个错误。
示例 2网页抓取
假设你正在编写一个用于从网站抓取数据的程序。然而该网站有时响应较慢或者根本不响应。你可以使用上下文来防止你的程序陷入困境。
func scrapeWebsite(ctx context.Context, url string) (*html.Node, error) {// Create a new context that will be cancelled if it takes more than 5 secondsctx, cancel : context.WithTimeout(ctx, 5*time.Second)defer cancel()// Create a request with the contextreq, err : http.NewRequestWithContext(ctx, http.MethodGet, url, nil)if err ! nil {return nil, err}// Execute the requestresp, err : http.DefaultClient.Do(req)if err ! nil {return nil, err}defer resp.Body.Close()// Parse the response body as HTMLreturn html.Parse(resp.Body), nill
}在这个示例中如果从网站获取数据超过5秒上下文将被取消http.DefaultClient.Do 应该返回一个错误。
示例3长时间运行的任务
假设你有一个执行长时间运行任务的程序但你希望能够在程序接收到关闭信号时停止任务。这在一个 Web 服务器中可能会很有用当关闭时必须停止提供请求并进行清理。
func doTask(ctx context.Context) {for {select {case -time.After(1 * time.Second):// The task is done, were ready to exitfmt.Println(Task is done)returncase -ctx.Done():// The context was cancelled from the outside, clean up and exitfmt.Println(Got cancel signal, cleaning up)return}}
}func main() {// Create a new contextctx, cancel : context.WithCancel(context.Background())// Start the task in a goroutinego doTask(ctx)// Wait for a shutdown signal-getShutdownSignal()// Cancel the context, which will stop the taskcancel()// Wait for a bit to allow the task to clean uptime.Sleep(1 * time.Second)
}在这个示例中当程序接收到关闭信号时它会取消上下文这会导致 doTask 在 -ctx.Done() 上接收到信号。
示例4HTTP 服务器
假设你正在构建一个处理传入请求的 HTTP 服务器。一些请求可能需要很长时间来处理你希望设置一个最长处理时间限制。
http.HandleFunc(/, func(w http.ResponseWriter, r *http.Request) {ctx, cancel : context.WithTimeout(r.Context(), 2*time.Second)defer cancel()// Simulate a long-running operationselect {case -time.After(3 * time.Second):w.Write([]byte(Operation finished.))case -ctx.Done():w.Write([]byte(Operation timed out.))}
})http.ListenAndServe(:8080, nil)在这个示例中如果操作需要超过2秒的时间上下文将被取消并且服务器将响应“操作超时”。
示例5同步多个 Goroutines
假设你正在编写一个程序使用 Goroutines 并发执行多个任务。如果其中一个任务失败你希望取消所有其他任务。
func doTask(ctx context.Context, id int) {select {case -time.After(time.Duration(rand.Intn(4)) * time.Second):fmt.Printf(Task %v finished.\n, id)case -ctx.Done():fmt.Printf(Task %v cancelled.\n, id)}
}func main() {ctx, cancel : context.WithCancel(context.Background())for i : 1; i 5; i {go doTask(ctx, i)}// Cancel the context after 2 secondstime.Sleep(2 * time.Second)cancel()// Give the tasks some time to finish uptime.Sleep(1 * time.Second)
}在这个示例中当上下文被取消时仍在运行的任何任务都将收到 -ctx.Done()从而允许它们进行清理并退出。
仍然在尝试理解吗
当我第一次接触上下文时我感到非常困惑我提出了一个问题即如果 select 前面的命令花费太长时间那么我们永远无法检测到 取消这是一个合理的问题。因此我准备了另一个示例来详细解释这种情况。
package mainimport (contextfmtmath/randtime
)func expensiveCalculation(ctx context.Context, resultChan chan- int) {// Simulate a long-running calculationrand.Seed(time.Now().UnixNano())sleepTime : time.Duration(rand.Intn(20)1) * time.Secondfmt.Printf(Calculation will take %s to complete\n, sleepTime)time.Sleep(sleepTime)select {case -ctx.Done():// Context was cancelled, dont write to the channelreturndefault:// Write the result to the channelresultChan - 42 // replace with your actual calculation result}
}func main() {// Create a context that will be cancelled after 10 secondsctx, cancel : context.WithTimeout(context.Background(), 10*time.Second)defer cancel() // The cancel should be deferred so resources are cleaned upresultChan : make(chan int)// Start the expensive calculation in a separate goroutinego expensiveCalculation(ctx, resultChan)// Wait for either the result or the context to be doneselect {case res : -resultChan:// Got the resultfmt.Printf(Calculation completed with result: %d\n, res)case -ctx.Done():// Context was cancelledfmt.Println(Calculation cancelled)}
}time.Sleep(sleepTime) 命令是阻塞的将暂停 goroutine 的执行直到指定的持续时间已过。这意味着 select 语句不会被执行直到休眠时间已经过去。
然而上下文的取消与 goroutine 内的执行是独立的。如果上下文的截止时间被超过或其 cancel() 函数被调用它的 Done() 通道将被关闭。
在主 goroutine 中您有另一个 select 语句它将立即检测上下文的 Done() 通道是否已关闭并在不等待 expensiveCalculation goroutine 完成休眠的情况下打印 “Calculation cancelled”。
也就是说expensiveCalculation goroutine 将在休眠后继续执行它将在尝试写入 resultChan 之前检查上下文是否已被取消。如果已被取消它将立即返回。这是为了避免潜在的死锁如果没有其他goroutine从 resultChan 读取。
如果需要昂贵的计算在本例中由 time.Sleep 模拟在取消时立即停止您必须设计计算以周期性地检查上下文是否已取消。这通常在需要将计算分解为较小部分的情况下使用循环。如果计算不能分解并需要一次运行完毕那么很遗憾在 Go 中无法提前停止它。