Chuyển đến nội dung chính
Mô-đun 7 · Bài học 3 trong tổng số 533/50 trong khóa học~12 min
Bài học theo mô-đun (3/5)

`select`: ghép kênh

select chờ hoạt động trên nhiều kênh cùng một lúc. Đó là switch của tính đồng thời của cờ vây: nó cho phép bạn phản ứng với lần đầu tiên kênh đã sẵn sàng trong số nhiều kênh.

Cú pháp

Go
select {
case v := <-ch1:
    fmt.Println("ch1:", v)
case v := <-ch2:
    fmt.Println("ch2:", v)
case ch3 <- 99:
    fmt.Println("inviato su ch3")
case <-time.After(time.Second):
    fmt.Println("timeout")
default:
    fmt.Println("nessuno è pronto")
}

Ngữ nghĩa:

  • Tất cả các trường hợp đều được đánh giá; chỉ một trong số các trường hợp sẵn sàng được chọn.
  • Nếu nhiều trường hợp đã sẵn sàng, lựa chọn là giả ngẫu nhiên (không có thứ tự).
  • Nếu chưa có trường hợp nào sẵn sàng: chặn select cho đến khi một trường hợp sẵn sàng.
  • Nếu có default: nó sẽ chạy ngay lập tức khi không có trường hợp nào sẵn sàng (không chặn chọn).

Mẫu thời gian chờ

Go
select {
case res := <-fetch(url):
    use(res)
case <-time.After(2 * time.Second):
    log.Println("timeout: fetch troppo lento")
}

time.After(d) trả về <-chan Time tạo ra giá trị sau d. Đó là cách cổ điển để hạn chế sự chờ đợi.

Chọn không chặn theo mặc định

Go
select {
case msg := <-ch:
    process(msg)
default:
    // niente da fare, prosegui
}

Nó "thăm dò" kênh mà không chặn. Hữu ích trong các đường dẫn mã phải luôn đáp ứng mà không bắt buộc phải chờ đợi.

Mẫu "Kênh hoàn tất"

Kết hợp select với kênh hủy:

Go
func worker(in <-chan Job, done <-chan struct{}) {
    for {
        select {
        case j := <-in:
            process(j)
        case <-done:
            return       // chi gestisce done segnala lo shutdown
        }
    }
}

close(done) từ điều phối viên sẽ kích hoạt case <-done trong mọi goroutine chờ: phối hợp tắt máy.

Lặp lại với lựa chọn

Go
for {
    select {
    case v := <-input:
        if v == nil {
            return
        }
        handle(v)
    case <-time.After(5 * time.Second):
        keepAlive()
    case <-ctx.Done():
        return
    }
}

Mẫu "vòng lặp sự kiện" điển hình dành cho máy chủ và nhân viên.

Kênh không là trường hợp "bị vô hiệu hóa"

Thủ thuật nâng cao: case <-ch với ch == nil là không bao giờ đã chọn. Nó cho phép bạn "vô hiệu hóa" một trường hợp một cách linh hoạt:

Go
var in chan int = source()
for {
    select {
    case v, ok := <-in:
        if !ok {
            in = nil    // disabilita questo case
            continue
        }
        handle(v)
    case <-ctx.Done():
        return
    }
}

Khi in bị đóng, tôi vô hiệu hóa nó: select chỉ tiếp tục xử lý các trường hợp khác.

Hãy thử nó

tập thể dục#go.m7.l3.e1
Nỗ lực: 0Đang tải…

Thực hiện lựa chọn với hai trường hợp nhận từ ch1 và ch2; in cái nào đến trước.

Đang tải trình chỉnh sửa…
Hiển thị gợi ý

Cú pháp `select { case ...: ... }` với một trường hợp trên mỗi kênh.

Giải pháp khả dụng sau 3 lần thử

tập thể dục#go.m7.l3.e2
Nỗ lực: 0Đang tải…

Thêm trường hợp hết thời gian chờ vào lựa chọn bằng cách sử dụng time.After(time.Second).

Đang tải trình chỉnh sửa…
Hiển thị gợi ý

`time.After(d)` là kênh nhận sau d.

Giải pháp khả dụng sau 3 lần thử

Quiz#go.m7.l3.e3
Sẵn sàng

Điều gì làm cho việc chọn không bị chặn?

Go
select { case v := <-ch: ... ??? }
Tùy chọn trả lời

Tóm tắt lại

  • select chờ trường hợp sẵn sàng đầu tiên trong số một số hoạt động kênh.
  • Lựa chọn giả ngẫu nhiên trong số nhiều trường hợp sẵn sàng.
  • default → chọn không chặn (bỏ phiếu).
  • time.After(d) khi hết thời gian chờ (lưu ý đến rò rỉ trong các vòng lặp: sử dụng context).
  • Các mẫu: kênh xong, vòng lặp sự kiện, kênh nil để vô hiệu hóa một trường hợp.
  • Trống select{} = khối vĩnh viễn: hầu như luôn là lỗi.