自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Golang 中的 Context 包

開發(fā) 后端
今天,我們將討論 Go 編程中非常重要的一個(gè)主題:context 包。如果你現(xiàn)在覺得它很令人困惑,不用擔(dān)心 — 在本文結(jié)束時(shí),你將像專家一樣處理 context!

今天,我們將討論 Go 編程中非常重要的一個(gè)主題:context 包。如果你現(xiàn)在覺得它很令人困惑,不用擔(dān)心 — 在本文結(jié)束時(shí),你將像專家一樣處理 context!

想象一下,你在一個(gè)主題公園,興奮地準(zhǔn)備搭乘一座巨大的過山車。但有個(gè)問題:排隊(duì)的人非常多,而且公園快要關(guān)門,你只有一個(gè)小時(shí)的時(shí)間。你會(huì)怎么辦?嗯,你可能會(huì)等一會(huì)兒,但不會(huì)等一個(gè)小時(shí),對(duì)吧?如果你等了 30 分鐘還沒有到前面,你會(huì)離開隊(duì)伍去嘗試其他游樂設(shè)施。這就是我們所謂的 '超時(shí)'。

現(xiàn)在,想象一下,你還在排隊(duì),突然下起了傾盆大雨。過山車的操作員決定關(guān)閉過山車。你不會(huì)繼續(xù)排隊(duì)等待根本不會(huì)發(fā)生的事情,對(duì)吧?你會(huì)立刻離開隊(duì)伍。這就是我們所謂的 '取消'。

在編程世界中,我們經(jīng)常面臨類似的情況。我們要求程序執(zhí)行可能需要很長(zhǎng)時(shí)間或需要因某種原因停止的任務(wù)。這就是 context 包發(fā)揮作用的地方。它允許我們優(yōu)雅地處理這些超時(shí)和取消。

它是如何工作的

(1) 創(chuàng)建上下文:我們首先創(chuàng)建一個(gè)上下文。這就像排隊(duì)等待過山車一樣。

ctx := context.Background() // This gives you an empty context

(2) 設(shè)置超時(shí):接下來(lái),我們可以在上下文中設(shè)置超時(shí)。這就好比你決定在排隊(duì)多久后放棄并去嘗試其他游樂設(shè)施。

ctxWithTimeout, cancel := context.WithTimeout(ctx, time.Second*10) // Wait for 10 seconds
// Don't forget to call cancel when you're done, or else you might leak resources!
defer cancel()

(3) 檢查超時(shí):現(xiàn)在,我們可以使用上下文來(lái)檢查是否等待時(shí)間太長(zhǎng),是否應(yīng)該停止我們的任務(wù)。這就好比在排隊(duì)等待時(shí)看看手表。

select {
case <-time.After(time.Second * 15): // This task takes 15 seconds
    fmt.Println("Finished the task")
case <-ctxWithTimeout.Done():
    fmt.Println("We've waited too long, let's move on!") // We only wait for 10 seconds
}

(4) 取消上下文:最后,如果出于某種原因需要停止任務(wù),我們可以取消上下文。這就好比聽到因下雨而宣布過山車關(guān)閉。

cancel() // We call the cancel function we got when we created our context with timeout

示例 1:慢速數(shù)據(jù)庫(kù)查詢

想象一下構(gòu)建一個(gè)從數(shù)據(jù)庫(kù)中獲取用戶數(shù)據(jù)的Web應(yīng)用程序。有時(shí),數(shù)據(jù)庫(kù)響應(yīng)較慢,你不希望用戶永遠(yuǎn)等下去。在這種情況下,你可以使用帶有超時(shí)的上下文。

func getUser(ctx context.Context, id int) (*User, error) {
    // Create a new context that will be cancelled if it takes more than 3 seconds
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()

    // Assume db.QueryRowContext is a function that executes a SQL query and returns a row
    row := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = ?", id)

    var name string
    if err := row.Scan(&name); err != nil {
        return nil, err
    }

    return &User{Name: name}, nil
}

在這個(gè)示例中,如果數(shù)據(jù)庫(kù)查詢花費(fèi)超過3秒的時(shí)間,上下文將被取消,db.QueryRowContext 應(yīng)返回一個(gè)錯(cuò)誤。

示例 2:網(wǎng)頁(yè)抓取

假設(shè)你正在編寫一個(gè)用于從網(wǎng)站抓取數(shù)據(jù)的程序。然而,該網(wǎng)站有時(shí)響應(yīng)較慢,或者根本不響應(yīng)。你可以使用上下文來(lái)防止你的程序陷入困境。

func scrapeWebsite(ctx context.Context, url string) (*html.Node, error) {
    // Create a new context that will be cancelled if it takes more than 5 seconds
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    // Create a request with the context
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    if err != nil {
        return nil, err
    }

    // Execute the request
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    // Parse the response body as HTML
    return html.Parse(resp.Body), nill
}

在這個(gè)示例中,如果從網(wǎng)站獲取數(shù)據(jù)超過5秒,上下文將被取消,http.DefaultClient.Do 應(yīng)該返回一個(gè)錯(cuò)誤。

示例3:長(zhǎng)時(shí)間運(yùn)行的任務(wù)

假設(shè)你有一個(gè)執(zhí)行長(zhǎng)時(shí)間運(yùn)行任務(wù)的程序,但你希望能夠在程序接收到關(guān)閉信號(hào)時(shí)停止任務(wù)。這在一個(gè) Web 服務(wù)器中可能會(huì)很有用,當(dāng)關(guān)閉時(shí)必須停止提供請(qǐng)求并進(jìn)行清理。

func doTask(ctx context.Context) {
    for {
        select {
        case <-time.After(1 * time.Second):
            // The task is done, we're ready to exit
            fmt.Println("Task is done")
            return
        case <-ctx.Done():
            // The context was cancelled from the outside, clean up and exit
            fmt.Println("Got cancel signal, cleaning up")
            return
        }
    }
}

func main() {
    // Create a new context
    ctx, cancel := context.WithCancel(context.Background())

    // Start the task in a goroutine
    go doTask(ctx)

    // Wait for a shutdown signal
    <-getShutdownSignal()

    // Cancel the context, which will stop the task
    cancel()

    // Wait for a bit to allow the task to clean up
    time.Sleep(1 * time.Second)
}

在這個(gè)示例中,當(dāng)程序接收到關(guān)閉信號(hào)時(shí),它會(huì)取消上下文,這會(huì)導(dǎo)致 doTask 在 <-ctx.Done() 上接收到信號(hào)。

示例4:HTTP 服務(wù)器

假設(shè)你正在構(gòu)建一個(gè)處理傳入請(qǐng)求的 HTTP 服務(wù)器。一些請(qǐng)求可能需要很長(zhǎng)時(shí)間來(lái)處理,你希望設(shè)置一個(gè)最長(zhǎng)處理時(shí)間限制。

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
    defer cancel()

    // Simulate a long-running operation
    select {
    case <-time.After(3 * time.Second):
        w.Write([]byte("Operation finished."))
    case <-ctx.Done():
        w.Write([]byte("Operation timed out."))
    }
})

http.ListenAndServe(":8080", nil)

在這個(gè)示例中,如果操作需要超過2秒的時(shí)間,上下文將被取消,并且服務(wù)器將響應(yīng)“操作超時(shí)”。

示例5:同步多個(gè) Goroutines

假設(shè)你正在編寫一個(gè)程序,使用 Goroutines 并發(fā)執(zhí)行多個(gè)任務(wù)。如果其中一個(gè)任務(wù)失敗,你希望取消所有其他任務(wù)。

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 seconds
    time.Sleep(2 * time.Second)
    cancel()

    // Give the tasks some time to finish up
    time.Sleep(1 * time.Second)
}

在這個(gè)示例中,當(dāng)上下文被取消時(shí),仍在運(yùn)行的任何任務(wù)都將收到 <-ctx.Done(),從而允許它們進(jìn)行清理并退出。

仍然在嘗試?yán)斫鈫幔?/h4>

當(dāng)我第一次接觸上下文時(shí),我感到非常困惑,我提出了一個(gè)問題,即如果 select 前面的命令花費(fèi)太長(zhǎng)時(shí)間,那么我們永遠(yuǎn)無(wú)法檢測(cè)到 取消,這是一個(gè)合理的問題。因此,我準(zhǔn)備了另一個(gè)示例來(lái)詳細(xì)解釋這種情況。

package main

import (
 "context"
 "fmt"
 "math/rand"
 "time"
)

func expensiveCalculation(ctx context.Context, resultChan chan<- int) {
 // Simulate a long-running calculation
 rand.Seed(time.Now().UnixNano())
 sleepTime := time.Duration(rand.Intn(20)+1) * time.Second
 fmt.Printf("Calculation will take %s to complete\n", sleepTime)

 time.Sleep(sleepTime)

 select {
 case <-ctx.Done():
  // Context was cancelled, don't write to the channel
  return
 default:
  // Write the result to the channel
  resultChan <- 42 // replace with your actual calculation result
 }
}

func main() {
 // Create a context that will be cancelled after 10 seconds
 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 defer cancel() // The cancel should be deferred so resources are cleaned up

 resultChan := make(chan int)

 // Start the expensive calculation in a separate goroutine
 go expensiveCalculation(ctx, resultChan)

 // Wait for either the result or the context to be done
 select {
 case res := <-resultChan:
  // Got the result
  fmt.Printf("Calculation completed with result: %d\n", res)
 case <-ctx.Done():
  // Context was cancelled
  fmt.Println("Calculation cancelled")
 }
}

time.Sleep(sleepTime) 命令是阻塞的,將暫停 goroutine 的執(zhí)行,直到指定的持續(xù)時(shí)間已過。這意味著 select 語(yǔ)句不會(huì)被執(zhí)行,直到休眠時(shí)間已經(jīng)過去。

然而,上下文的取消與 goroutine 內(nèi)的執(zhí)行是獨(dú)立的。如果上下文的截止時(shí)間被超過或其 cancel() 函數(shù)被調(diào)用,它的 Done() 通道將被關(guān)閉。

在主 goroutine 中,您有另一個(gè) select 語(yǔ)句,它將立即檢測(cè)上下文的 Done() 通道是否已關(guān)閉,并在不等待 expensiveCalculation goroutine 完成休眠的情況下打印 **"Calculation cancelled"**。

也就是說,expensiveCalculation goroutine 將在休眠后繼續(xù)執(zhí)行,它將在嘗試寫入 resultChan 之前檢查上下文是否已被取消。如果已被取消,它將立即返回。這是為了避免潛在的死鎖,如果沒有其他goroutine從 resultChan 讀取。

如果需要昂貴的計(jì)算(在本例中由 time.Sleep 模擬)在取消時(shí)立即停止,您必須設(shè)計(jì)計(jì)算以周期性地檢查上下文是否已取消。這通常在需要將計(jì)算分解為較小部分的情況下使用循環(huán)。如果計(jì)算不能分解,并需要一次運(yùn)行完畢,那么很遺憾,在 Go 中無(wú)法提前停止它。

責(zé)任編輯:趙寧寧 來(lái)源: 技術(shù)的游戲
相關(guān)推薦

2023-05-12 09:40:53

ContextGolang

2023-05-15 08:50:58

ContextGolang

2021-04-28 09:02:48

Golang語(yǔ)言Context

2023-10-24 16:03:34

GoGolang

2023-08-03 08:48:07

Golang接口

2023-11-27 15:02:37

BytesGolang

2023-08-02 09:07:27

Golangio 包

2023-10-18 08:22:38

BufioGolang

2023-09-06 09:10:04

Golang字符串

2023-09-07 07:35:54

GolangBufio

2023-11-07 09:02:07

Golangbytes

2023-09-04 08:17:37

Golangstrings 包

2023-08-28 17:16:51

Golangio 包

2023-11-03 08:53:15

StrconvGolang

2023-09-05 08:22:44

Golangstrings 包

2023-10-07 09:08:32

Golangbufio

2023-10-10 08:57:44

Golangbufio

2024-01-18 09:07:04

Errors函數(shù)Golang

2014-08-07 10:06:05

ndroidContext

2023-08-08 14:51:29

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)