在 Go 中用 Context 取消操作

來源:互聯網
上載者:User
許多使用 Go 的人都會遇到 context 包。大多數時候 context 用在下遊操作, 比如發送 Http 請求、查詢資料庫、或者開 go-routines 執行非同步作業。最普通用法是通過它向下遊操作傳遞資料。很少人知道,但是非常有用的context功能是在執行中取消或者停止操作。這篇文章會解釋我們如何使用 Context 的取消功能,還有通過一些 Context 使用方法和最佳實務讓你的應用更加快速和健壯。## 我們為什麼需要取消操作?簡單來說,我們需要取消來避免系統做無用的操作。想像一下,一般的http應用,使用者請求 Http Server, 然後 Http Server查詢資料庫並返回資料給用戶端:![http 應用](https://raw.githubusercontent.com/studygolang/gctt-images/master/using-context-cancellation-in-go/1.png)如果每一步都很完美,耗時時圖會像下面這樣:![耗時圖](https://raw.githubusercontent.com/studygolang/gctt-images/master/using-context-cancellation-in-go/2.png)但是,如果用戶端中途插斷要求會發生什嗎?會發生,比如: 請求中途,用戶端關閉了瀏覽器。如果沒有取消操作,Application Server 和 資料庫 會繼續他們的工作,儘管工作的結果會被浪費。![異常耗時圖](https://raw.githubusercontent.com/studygolang/gctt-images/master/using-context-cancellation-in-go/3.png)理想條件下,如果我們知道流程(例子中的 http request)的話, 我們想要下遊操作也會停止:![理想耗時圖](https://raw.githubusercontent.com/studygolang/gctt-images/master/using-context-cancellation-in-go/4.png)## go context包的取消操作現在我們知道為什麼需要取消操作了,讓我們看看在 Golang 裡如何?。因為"取消操作"高度依賴上下文,或者已執行的操作,所以它非常容易通過 context 包來實現。有兩個步驟你需要實現:1. 監聽取消事件2. 觸發取消事件## 監聽取消事件_context_ 包提供了 _Done()_ 方法, 它返回一個當 Context 收取到 _取消_ 事件時會接收到一個 _struct{}_ 類型的 _channel_。監聽取消事件只需要簡單的等待 _<- ctx.Done()_ 就好了例如: 一個 Http Server 會花2秒去處理事務,如果請求提前取消,我們想立馬返回結果:```gofunc main() {// Create an HTTP server that listens on port 8000http.ListenAndServe(":8000", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {ctx := r.Context()// This prints to STDOUT to show that processing has startedfmt.Fprint(os.Stdout, "processing request\n")// We use `select` to execute a peice of code depending on which// channel receives a message firstselect {case <-time.After(2 * time.Second):// If we receive a message after 2 seconds// that means the request has been processed// We then write this as the responsew.Write([]byte("request processed"))case <-ctx.Done():// If the request gets cancelled, log it// to STDERRfmt.Fprint(os.Stderr, "request cancelled\n")}}))}```> 原始碼地址: https://github.com/sohamkamani/blog-example-go-context-cancellation你可以通過執行這段代碼, 用瀏覽器開啟 [localhost:8000](http://localhost:8000)。如果你在2秒內關閉瀏覽器,你會看到在控制台列印了 "request canceled"。## 觸發取消事件如果你有一個可以取消的操作,你可以通過context觸發一個 _取消事件_ 。 這個你可以用 context 包 提供的 _WithCancel_ 方法, 它返回一個 context 對象,和一個沒有參數的方法。這個方法不會返回任何東西,僅在你想取消這個context的時候去調用。第二種情況是依賴。 依賴的意思是,當一個操作失敗,會導致其他動作失敗。 例如:我們提前知道了一個操作失敗,我們會取消所有依賴操作。```gofunc operation1(ctx context.Context) error {// Let's assume that this operation failed for some reason// We use time.Sleep to simulate a resource intensive operationtime.Sleep(100 * time.Millisecond)return errors.New("failed")}func operation2(ctx context.Context) {// We use a similar pattern to the HTTP server// that we saw in the earlier exampleselect {case <-time.After(500 * time.Millisecond):fmt.Println("done")case <-ctx.Done():fmt.Println("halted operation2")}}func main() {// Create a new contextctx := context.Background()// Create a new context, with its cancellation function// from the original contextctx, cancel := context.WithCancel(ctx)// Run two operations: one in a different go routinego func() {err := operation1(ctx)// If this operation returns an error// cancel all operations using this contextif err != nil {cancel()}}()// Run operation2 with the same context we use for operation1operation2(ctx)}```## 基於時間的取消操作任何程式對一個請求的最大處理時間都需要維護一個 SLA (服務等級協定),這可以使用基於時間的取消。這個 API 基本和上一個例子相同,只是多了一點點:```go// The context will be cancelled after 3 seconds// If it needs to be cancelled earlier, the `cancel` function can// be used, like beforectx, cancel := context.WithTimeout(ctx, 3*time.Second)// The context will be cancelled on 2009-11-10 23:00:00ctx, cancel := context.WithDeadline(ctx, time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC))```例如:HTTP API 呼叫內部的服務。 如果服務長時間沒有響應,最好提前返回失敗並取消請求。```gofunc main() {// Create a new context// With a deadline of 100 millisecondsctx := context.Background()ctx, _ = context.WithTimeout(ctx, 100*time.Millisecond)// Make a request, that will call the google homepagereq, _ := http.NewRequest(http.MethodGet, "http://google.com", nil)// Associate the cancellable context we just created to the requestreq = req.WithContext(ctx)// Create a new HTTP client and execute the requestclient := &http.Client{}res, err := client.Do(req)// If the request failed, log to STDOUTif err != nil {fmt.Println("Request failed:", err)return}// Print the statuscode if the request succeedsfmt.Println("Response received, status code:", res.StatusCode)}```根據 google 首頁對您的請求的響應速度, 您將收到:```Response received, status code: 200```或者```Request failed: Get http://google.com: context deadline exceeded```你可以通過設定逾時來獲得以上2種結果。## 陷阱和注意事項儘管 Go 的 context 很好用,但是在使用之前最好記住幾點。最重要的就是:context 僅可以取消一次。如果想傳播多個錯誤的話,context 取消並不是最好的選擇,最慣用的情境是你真的想取消一個操作,並通知下遊操作發生了一個錯誤。另一個要記住的是,一個 context 執行個體會貫穿所有你想使用取消操作的方法和 go-routines 。要避免使用一個已取消的 context 作為 _WithTimeout_ 或者 _WithCancel_ 的參數,這可能導致不確定的事情發生。

via: https://www.sohamkamani.com/blog/golang/2018-06-17-golang-using-context-cancellation/

作者:Soham Kamani 譯者:nelsonken 校對:polaris1119

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽

202 次點擊  
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.