Home Arrow Icon Knowledge base Arrow Icon Global Arrow Icon Làm thế nào để các hoạt động kênh xử lý thời gian chạy trong nội bộ


Làm thế nào để các hoạt động kênh xử lý thời gian chạy trong nội bộ


Các hoạt động của Kênh thời gian chạy GO trong nội bộ thông qua sự phối hợp tinh vi của các cấu trúc dữ liệu, cơ chế khóa, lập lịch goroutine và đồng bộ hóa để cung cấp giao tiếp hiệu quả, công bằng và không có bế tắc giữa các goroutines.

Các kênh trong GO chủ yếu được triển khai dưới dạng cấu trúc dữ liệu `hchan`, gói gọn trạng thái của kênh bao gồm bộ đệm, hàng đợi người gửi và máy thu đang chờ, các nguyên thủy đồng bộ hóa và thông tin meta như liệu kênh có bị đóng hay không. Khi một kênh được tạo, một thể hiện của `hchan` được phân bổ trên đống và giá trị đại diện cho các điểm kênh cho cấu trúc này, cho phép nhiều goroutines tham chiếu và sử dụng nó đồng thời.

Tại cốt lõi, một kênh bao gồm:

- Một bộ đệm tròn cho các kênh được đệm, được biểu thị bằng một lát cắt và chỉ số `sendx` và` recvx` theo dõi nơi sẽ gửi và nhận các yếu tố, tương ứng. Kích thước bộ đệm được cố định khi tạo kênh và nó hoạt động như một hàng đợi lưu trữ các yếu tố được gửi nhưng chưa nhận được.
- Hai hàng đợi FIFO chờ (danh sách được liên kết) để quản lý các goroutin bị chặn khi gửi (`sendq`) và nhận (` recvq`). Các hàng đợi này lưu trữ các cấu trúc `sudog`, đại diện cho goroutine bị chặn và dữ liệu liên quan cho các hoạt động kênh như giá trị để gửi hoặc một con trỏ đến nơi nhận được giá trị.
- Khóa mutex để bảo vệ truy cập đồng thời vào các cấu trúc bên trong của kênh, đảm bảo rằng các hoạt động gửi và nhận duy trì tính nhất quán và không làm hỏng trạng thái của kênh dưới quyền truy cập đồng thời.

Hoạt động gửi kênh

Khi một goroutine cố gắng gửi một giá trị đến một kênh (`ch e hoạt động

Hoạt động nhận (`từ vựng:

1. Mua lại khóa: Mutex của kênh bị khóa.

2. Kiểm tra người gửi đang chờ: Nếu có người gửi đang chờ trong `sendq`:
- Người nhận lấy giá trị trực tiếp từ `sudog` của người gửi.
- Người gửi được dequeued và được đánh dấu Runnable.
- Cả hai goroutines tiến hành ngay lập tức mà không cần đệm.

3. Kiểm tra bộ đệm cho các kênh đệm: Nếu không có người gửi đang chờ:
- Kênh kiểm tra nếu bộ đệm của nó chứa bất kỳ yếu tố nào.
- Nếu vậy, một phần tử được sao chép từ vị trí bộ đệm `Buf [recvx]`.
- Chỉ số `recvx` được tăng lên và` Qcount` bị giảm.
- Khóa được phát hành và máy thu tiếp tục mà không chặn.

4. Chặn máy thu: Nếu bộ đệm trống và không có người gửi đang chờ:
- Goroutine người nhận được đại diện dưới dạng `sudog`.
- Nó được enqueued trong `recvq`.
- Máy thu được đặt bởi bộ lập lịch cho đến khi một hoạt động gửi bỏ chặn nó.

5. Kênh đóng nhận: Nếu kênh được đóng và bộ đệm trống:
- Nhận trả về giá trị 0 của loại phần tử.
- Người nhận không chặn và có thể phát hiện trạng thái đóng bởi Boolean thứ hai được trả về từ hoạt động nhận.

6. Sự công bằng và trật tự: Người nhận chờ được bảo vệ FIFO để duy trì sự công bằng và lịch trình tôn trọng các ràng buộc đặt hàng nhưng không đảm bảo thứ tự thời gian nghiêm ngặt do hành vi của người lập lịch.

Tích hợp đỗ xe và lập lịch

Các kênh được tích hợp chặt chẽ với bộ lập lịch Goroutine của Go, quản lý vòng đời Goroutine bằng ba thực thể:

- G (Goroutine): Chủ đề nhẹ, cấp độ người dùng.
- M (máy): Chủ đề HĐH thực thi goroutines.
- P (Bộ xử lý): Giữ một hàng đợi cục bộ của các goroutines và tài nguyên cần thiết để thực thi mã GO.

Khi một hoạt động kênh khối (có thể gửi hoặc nhận), goroutine:

- được đánh dấu là chờ đợi.
- được xóa khỏi hàng đợi chạy cục bộ của `p`.
- được liên kết thông qua cấu trúc `sudog` trong hàng đợi kênh tương ứng (` sendq` hoặc `recvq`).
- Bộ lập lịch sau đó chọn một goroutine có thể chạy được để chạy trên đó là `p`.

Khi thao tác bị chặn đã sẵn sàng để hoàn thành (ví dụ: một đối tác gửi hoặc nhận xảy ra):

- Goroutine đang chờ được khử từ hàng đợi kênh.
- Đánh dấu Runnable.
- Được đặt trở lại trên một hàng đợi chạy địa phương hoặc toàn cầu để lên lịch.
- Goroutine cuối cùng sẽ tiếp tục thực hiện.

Thiết kế này tránh được sự chờ đợi bận rộn và đảm bảo chuyển đổi ngữ cảnh hiệu quả với chi phí tối thiểu so với chặn chủ đề hệ điều hành truyền thống.

Hoạt động đóng kênh

Đóng một kênh đặt cờ `đóng` trong` hchan` được bảo vệ bởi cùng một mutex. Đóng một kênh gây ra như sau:

- Tất cả những người gửi bị chặn hoảng loạn nếu cố gắng gửi.
- Tất cả các máy thu bị chặn đều được bỏ chặn theo thứ tự FIFO để nhận các giá trị được đệm còn lại và sau đó là các giá trị bằng không.
- Tiếp tục nhận lại các giá trị không trả lại ngay lập tức mà không chặn.
- Gửi trên một kênh khép kín hoảng loạn ngay lập tức.
- Hoạt động chặt chẽ cũng được đồng bộ hóa với các hoạt động kênh thông qua mutex để tránh các điều kiện đua.

Cấu trúc dữ liệu nội bộ

- HChan: Trung tâm tổ chức cấu trúc:
- `buf` (con trỏ mảng đệm)
- `dataqsiz` (kích thước bộ đệm)
- `Qcount` (số lượng các yếu tố hiện đang được đệm)
- `sendx`,` recvx` (chỉ số bộ đệm)
- `sendq`,` recvq` (Chờ đợi cho người gửi và người nhận bị chặn)
- `Cờ Đóng`
- Mutex nhúng (để bảo vệ trạng thái và phối hợp nhiều hoạt động đồng thời).

- Sudog: Cấu trúc thời gian chạy nội bộ đại diện cho một goroutine đang chờ hoạt động kênh. Nắm giữ:
- Một con trỏ đến Goroutine.
- Con trỏ dữ liệu được gửi hoặc nhận.
- Liên kết đến `sudog` tiếp theo trong hàng đợi chờ.

- Waitq: Danh sách các sudog được liên kết đại diện cho người phục vụ (`sendq`) hoặc người phục vụ nhận (` recvq`). Đảm bảo sự công bằng của FIFO.

Chọn câu lệnh và kênh

Câu lệnh `select` của GO giới thiệu sự phức tạp vì nhiều hoạt động kênh cạnh tranh đồng thời. Thời gian chạy quản lý nhiều hàng đợi chờ cho từng trường hợp chọn và chọn từ nhiều kênh ngoại tuyến liên quan đến:

- thử các hoạt động không chặn trên mỗi trường hợp.
- Nếu không có tiến hành, hãy tham gia vào goroutine trên tất cả các hàng đợi có liên quan.
- Khi một hoạt động có thể tiến hành, những người phục vụ khác sẽ được gỡ bỏ và các loại bỏ chặn goroutine.

Cơ chế chờ chọn lọc này được tích hợp sâu với bộ lập lịch thời gian chạy để duy trì sự công bằng và hiệu quả.

Cân nhắc hiệu suất và đồng thời

Thiết kế kênh cố gắng cân bằng giữa an toàn và hiệu suất:

- Khóa mutex: Đảm bảo tính nhất quán nhưng có thể trở thành một nút cổ chai dưới sự tranh chấp rất cao. Thời gian chạy giữ khóa được giữ ngắn gọn để cập nhật trạng thái nguyên tử.
- Handoff trực tiếp: Tránh bộ đệm trên đầu khi có thể, cho phép giao tiếp nhanh hơn giữa các goroutines.
- Đỗ xe và không chịớ: Giảm thiểu chi phí chuyển đổi ngữ cảnh so với các luồng hệ điều hành.
- Công bằng: Được thực thi thông qua hàng đợi FIFO để ngăn chặn đói.
- Bộ nhớ cache và địa phương: Các goroutines không được phát hành có thể tiếp tục trên các bộ xử lý khác nhau (`p `s), có thể dẫn đến bỏ lỡ bộ đệm.
- Vấn đề về đàn sấm sét: Đóng một kênh hoặc phát sóng có thể đánh thức đồng thời nhiều goroutines, điều này phải được xử lý một cách thận trọng bởi người lập lịch.

Các kênh đại diện cho một nguyên thủy đồng thời thanh lịch, kết hợp các bộ đệm vòng được bảo vệ khóa với các cơ chế lập kế hoạch goroutine, cung cấp an toàn, đặt hàng và hiệu quả trong các chương trình GO.

Các phần bên trong của các hoạt động kênh GO cho thấy sự kết hợp chặt chẽ giữa các cấu trúc dữ liệu, khóa và bộ lập lịch thời gian chạy, nhúng đồng bộ hóa trong các luồng nhẹ ở cấp độ người dùng và cho phép các mẫu giao tiếp phong phú mà không có tương tác kernel HĐH nặng. Cách tiếp cận này phân biệt mô hình đồng thời của GO và là một lý do chính các kênh vừa hiệu quả vừa đơn giản để sử dụng từ quan điểm của lập trình viên.