+2

Bằng cách nào Golang quản lý và vận hành Goroutine một cách hiệu quả

Go Scheduler — Under The Hood 🧠⚙️

Các bạn đã biết qua về Golang, đặc biệt là goroutine – "vũ khí tối thượng" của Go giúp viết các chương trình concurrent một cách dễ dàng và hiệu quả.

Trong bài viết này, mình không đi sâu vào ưu nhược điểm của goroutine, mà sẽ cùng tìm hiểu cách Go runtime quản lý các goroutines, cụ thể như:

  • Goroutine nào sẽ được thực thi trước?
  • Làm thế nào để scale lên/xuống?
  • Điều gì xảy ra khi một goroutine bị block?

1. Kiến trúc GPM (Goroutine, Processor, Machine)

Thành phần Vai trò
G (Goroutine) Đại diện cho một goroutine – chứa thông tin về ngăn xếp, trạng thái, và đoạn mã sẽ thực thi.
M (Machine) Là luồng hệ điều hành (OS thread) – thực thi goroutine thông qua P.
P (Processor) Bộ xử lý logic – quản lý local run queue và điều phối thực thi goroutine.

2. Sched là gì?

Sched là một struct toàn cục trong Go runtime, chịu trách nhiệm điều phối việc chạy của goroutine:

  • Quản lý Global Run Queue (hàng đợi toàn cục)
  • Danh sách các M đang hoạt động hoặc đang đợi
  • Danh sách các P đã cấp phát hoặc đang rảnh
  • Global locks, counters, thống kê...

Sched giống như một trung tâm điều phối giao thông, đảm bảo mọi thứ diễn ra trơn tru và công bằng giữa các goroutine.


3. Trạng thái của một Goroutine

Screenshot 2025-07-24 at 08.50.52.png

🟡 Runnable

Goroutine ở trạng thái này khi:

  • Được tạo bằng từ khóa go
  • Hoặc được đánh thức từ trạng thái Blocked (ví dụ sau khi đợi I/O hoặc mutex)

🔹 Lúc này, goroutine sẵn sàng được lên lịch, nhưng chưa thực sự chạy.


🟢 Running

Goroutine đang:

  • Thực thi mã trên OS thread thông qua P
  • Tiến hành xử lý logic của chương trình

🔴 Blocked

Goroutine rơi vào trạng thái này khi:

  • Chờ dữ liệu từ channel
  • Chờ lock (mutex)
  • Chờ thao tác I/O hoặc mạng hoàn thành

✅ Khi unblock, goroutine chuyển về lại trạng thái Runnable


4. M:N Scheduler — Cơ chế ghép goroutine và thread

Screenshot 2025-07-24 at 08.51.16.png

Go sử dụng mô hình M:N scheduler:

  • N goroutines được ánh xạ lên M luồng hệ điều hành (M)
  • Mỗi M thực thi qua P

⚙️ Khi chương trình bắt đầu:

  • Go runtime tạo ra số lượng thread (M) dựa trên phần cứng:
    số lõi CPU × số luồng phần cứng trên mỗi lõi.

Ví dụ: CPU 8 lõi, mỗi lõi 2 thread ⇒ 8 × 2 = 16 OS threads.

Bạn có thể điều chỉnh số lượng P bằng cách thiết lập biến môi trường GOMAXPROCS.


🔄 Về goroutine:

  • Có thể tạo hàng nghìn đến hàng triệu goroutines.
  • Goroutine rất nhẹ (khoảng 2KB stack ban đầu, tự mở rộng).
  • Điều kiện duy nhất: đủ bộ nhớ.

5. Go Scheduler — Under The Hood

Screenshot 2025-07-24 at 08.52.16.png

🧩 Processor

  • Gắn với một M
  • Có Local Run Queue (tối đa 256 goroutines)
  • Điều phối việc thực thi goroutines

🧵 M (Machine - OS Thread)

  • Là OS thread thực sự
  • Go runtime quản lý số lượng M tùy theo tải

🌐 Global Run Queue

  • Nơi chứa các goroutine chưa phân phối cho P
  • Nếu local queue của P trống → lấy từ Global Queue

🔧 Khi tạo goroutine mới

  • Go tạo struct G (Goroutine)
  • G sẽ được:
    • Đưa vào Local Run Queue nếu còn chỗ
    • Hoặc Global Run Queue nếu local queue đầy

6. Work Stealing — Trộm việc để cân bằng

  • Mỗi P thực thi các goroutine trong local queue.
  • Nếu local queue trống:
    • P sẽ "đánh cắp" ½ số goroutine từ local queue của P khác.
    • Nếu không có gì để "trộm" → lấy từ Global Run Queue.

✅ Kỹ thuật này giúp cân bằng tải và tận dụng CPU hiệu quả.


7. Blocking Calls — Khi goroutine bị chặn

Khi một goroutine gọi I/O, mạng, channel,...:

  • Goroutine sẽ bị descheduled
  • Chuyển sang trạng thái Blocked
  • Nhường tài nguyên cho goroutine khác

🛰 Ví dụ: chờ network

  • Goroutine chờ phản hồi mạng → đưa vào Network Queue
  • Một background thread giám sát queue này
  • Khi xong → goroutine được đưa lại vào Runnable Screenshot 2025-07-24 at 08.53.43.png

8. Thu gom bộ nhớ (Garbage Collection)

  • Khi goroutine kết thúc hoặc không còn tham chiếu đến → được GC thu gom
  • Tránh rò rỉ bộ nhớ (memory leak)

🧠 Kết luận

Go Scheduler là một hệ thống cực kỳ tối ưu, tự động và hiệu quả. Nó cho phép:

  • Viết các chương trình concurrent dễ dàng, không đau đầu quản lý thread
  • Khả năng mở rộng lớn nhờ lightweight goroutine
  • Xử lý I/O non-blocking một cách tự nhiên

Việc hiểu cách hoạt động "under the hood" sẽ giúp bạn:

  • Viết code tối ưu hơn
  • Tránh các lỗi khó chịu như deadlock, starvation
  • Debug hiệu quả hơn trong hệ thống lớn

📚 Tài liệu tham khảo


Cảm ơn Chatgpt đã giúp mình tạo nên markdown đẹp


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí