Context Package trong go 1.7
golang
38
Go
39
White

Hoàng Minh Trung viết ngày 22/07/2016

Ở golang 1.7 thì package http://golang.org/x/net/context đã được đưa vào dưới dạng là thư viện std. Thêm nũa thì nhiều package nằm trong std cũng đã đưa vào các method mới sử dụng package này. Cá nhân mình nghĩ là package context sẽ trở nên khá quan trọng về sau với các gopher.

Một cách ngắn gọn thì package context có thể hiểu là nhằm mục đích giải quyết "cancel" signal cho go routine một cách chính thống, mà bạn không phải implement bằng tay. Vấn đề cancel signal bạn có thể tự giải quyết bằng cách tự implement, tuy nhiên việc cung cấp một std để giải quyết việc này sẽ tốt hơn nhiều cho developer.

Ví dụ về việc tại sao lại cần package context

Nếu không sử dụng context

Chúng ta có một đoạn code ngắn dưới đây mô tả một hàm handler, nhận vào request, và với mỗi request sẽ tạo một goroutine mới xử lý request đó.

func handler(w http.ResponseWriter, r *http.Request) {
    errCh := make(chan error, 1)
    go func() {
        errCh <- request()
    }()
    select {
    case err := <-errCh:
        if err != nil {
            log.Println("failed:", err)
            return
        }
    }

    log.Println("success")
}

Giả sử với đoạn code trên, chúng ta muốn thêm xử lý timeout sau 2s, thì sẽ phải làm thế nào?

func handler(w http.ResponseWriter, r *http.Request) {
    errCh := make(chan error, 1)
    go func() {
        errCh <- request()
    }()

    select {
    case err := <-errCh:
        if err != nil {
            log.Println("failed:", err)
            return
        }


    //setting timeout sau 2s
    case <-time.After(2 * time.Second):
        log.Println("failed: timeout")
        return
    }

    log.Println("success")
}

Tuy nhiên đoạn code trên có điểm tồi là, nếu sau 2s timeout thì sẽ return, để lại bạn routine bơ vơ không người dạy dỗ. Nếu lượng request nhiều thì lượng routine bơ vơ càng nhiều, gây leak. Do vậy chúng ta cần phải đóng cái routine này lại một cách cẩn thận, bằng cách "cancel" cái request routine lại

func handler(w http.ResponseWriter, r *http.Request) {
    //tạo channel để cancel routine
    doneCh := make(chan struct{}, 1)

    errCh := make(chan error, 1)
    go func() {
        errCh <- request(doneCh)
    }()

    //setting timeout sử dụng một routine riêng biệt
    go func() {
        <-time.After(2 * time.Second)
        close(doneCh)
    }()

    select {
    case err := <-errCh:
        if err != nil {
            log.Println("failed:", err)
            return
        }
    }

    log.Println("success")
}

Và khi đó hàm request sẽ được viết như dưới đây

func request(doneCh chan struct{}) error {
    tr := &http.Transport{}
    client := &http.Client{Transport: tr}

    req, err := http.NewRequest("POST", backendService, nil)
    if err != nil {
        return err
    }
  
    errCh := make(chan error, 1)
    go func() {
        _, err := client.Do(req)
        errCh <- err
    }()

    select {
    case err := <-errCh:
        if err != nil {
            return err
        }
    //mấu chốt nằm ở đây
    case <-doneCh:
        tr.CancelRequest(req)
        <-errCh
        return fmt.Errorf("canceled")
    }

    return nil
}

Có vẻ phức tạp và dài dòng...
context package come to help~

Logic sử dụng context

func handler(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    errCh := make(chan error, 1)
    go func() {
        errCh <- request3(ctx)
    }()

    select {
    case err := <-errCh:
        if err != nil {
            log.Println("failed:", err)
            return
        }
    }

    log.Println("success")
}

Và request sẽ được viết lại như dưới đây

func request(ctx context.Context) error {
    tr := &http.Transport{}
    client := &http.Client{Transport: tr}

    req, err := http.NewRequest("POST", backendService, nil)
    if err != nil {
        return err
    }

    errCh := make(chan error, 1)
    go func() {
        _, err := client.Do(req)
        errCh <- err
    }()

    select {
    case err := <-errCh:
        if err != nil {
            return err
        }
    case <-ctx.Done():
        tr.CancelRequest(req)
        <-errCh
        return ctx.Err()
    }

    return nil
}

Đơn giản hơn đúng không nhỉ :-?

Tham khảo thêm ở đây: https://blog.golang.org/context

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Hoàng Minh Trung

23 bài viết.
65 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
22 1
Bài viết dịch từ http://arslan.io/tenusefultechniquesingo Sử dụng một GOPATH duy nhất Sử dụng đồng thời nhiều GOPATH sẽ không giúp cho hệ thống ...
Hoàng Minh Trung viết hơn 2 năm trước
22 1
White
19 15
(Ảnh) Mục đích của bài viết là hướng dẫn cơ bản nhất cho những ai chưa biết về docker, môi trường thực hiện là mac OS. Chuẩn bị Cài đặt virtua...
Hoàng Minh Trung viết hơn 3 năm trước
19 15
White
17 0
Bài viết dịch từ https://github.com/luciotato/golangnotes/blob/master/OOP.md Mục đích bài viết Học golang dễ dàng hơn với những kiến thức bạn đ...
Hoàng Minh Trung viết hơn 2 năm trước
17 0
Bài viết liên quan
White
16 0
Crawl dữ liệu Crawl là một vấn đề hay gặp trong quá trình làm software. Ví dụ lấy tin tức, tin giảm giá, vé xem phim... là những dạng của crawl. Mộ...
Thach Le viết hơn 2 năm trước
16 0
White
10 2
Makefile thực hiện một số thao tác thường dùng trong Go Khi làm project Go mình thường tạo một file Makefile dạng này: Lưu ý nhớ thay thành tên m...
Huy Trần viết hơn 2 năm trước
10 2
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
23 bài viết.
65 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!