
Ảnh: https://golangbyexample.com/goroutines-golang/
Trong Golang, OS Thread
và Goroutine
có mối quan hệ chặt chẽ nhưng không phải 1:1. Dưới đây là sự khác biệt và cách chúng hoạt động cùng nhau:
1. OS Thread
- Là các luồng do hệ điều hành quản lý.
- Mỗi OS thread hoạt động độc lập và được hệ điều hành lập lịch (
scheduler
) quản lý. - Tạo OS thread mới tốn nhiều tài nguyên hơn so với goroutine.
- Khi một OS thread bị block (chẳng hạn gọi một syscall chặn), nó sẽ không thể thực thi các công việc khác.
2. Goroutine
- Là các luồng nhẹ (
lightweight thread
) được quản lý bởi Golang runtime. - Một OS thread có thể chạy nhiều goroutines nhờ vào Golang Scheduler.
- Tạo và hủy goroutine nhanh hơn rất nhiều so với OS thread.
- Khi một goroutine bị block (ví dụ: chờ I/O), Golang runtime có thể chuyển sang goroutine khác trên cùng OS thread hoặc di chuyển goroutine sang một OS thread khác.
3. Mối Quan Hệ giữa OS Thread và Goroutine
- Golang runtime có một scheduler (
M:N scheduler
), giúp ánh xạN
goroutines vàoM
OS threads. - Ban đầu, Golang khởi tạo một số OS threads dựa trên giá trị của
GOMAXPROCS
(mặc định bằng số CPU cores). - Nếu một goroutine thực hiện một thao tác blocking (như I/O, syscall), runtime có thể tạo một OS thread mới để xử lý goroutine khác, tránh bị chặn.
4. Các Thành Phần Chính trong Golang Scheduler
- G (Goroutine): Đại diện cho một goroutine cụ thể.
- M (Machine – OS Thread): Một OS thread chạy các goroutines.
- P (Processor): Một bộ lập lịch giúp quản lý các goroutines trên một số lượng OS threads nhất định.
5. Ví dụ minh họa
goCopyEditpackage main
import (
"fmt"
"runtime"
"time"
)
func main() {
fmt.Println("Số CPU:", runtime.NumCPU()) // Số CPU
fmt.Println("Số Goroutine:", runtime.NumGoroutine()) // Số Goroutines hiện tại
for i := 0; i < 5; i++ {
go func(id int) {
fmt.Println("Goroutine:", id)
time.Sleep(time.Second)
}(i)
}
time.Sleep(2 * time.Second)
fmt.Println("Số Goroutine cuối cùng:", runtime.NumGoroutine())
}
Giải thích:
- Chương trình tạo 5 goroutines chạy trên một số OS threads được quản lý bởi Golang runtime.
- Scheduler phân phối goroutines lên các OS threads.
- Sau 1 giây, tất cả các goroutines kết thúc, chỉ còn lại goroutine chính.
6. Khi Nào Golang Tạo OS Thread Mới?
- Khi một goroutine thực hiện một syscall blocking, Golang runtime có thể tạo một OS thread mới để tránh chặn các goroutines khác.
- Nếu có nhiều goroutines cần thực thi hơn số lượng OS threads hiện có, runtime sẽ tận dụng OS threads sẵn có thay vì tạo mới.
- Sử dụng
runtime.LockOSThread()
để cố định một goroutine trên một OS thread (thường dùng cho tương tác với C hoặc GUI).
Tóm Tắt
Đặc điểm | OS Thread | Goroutine |
---|---|---|
Quản lý bởi | Hệ điều hành | Golang runtime |
Chi phí tạo | Cao | Thấp |
Chi phí chuyển đổi | Cao (context switch) | Thấp (scheduler Golang) |
Số lượng có thể tạo | Giới hạn bởi OS | Hàng triệu |
Chặn (blocking ) | Chặn toàn bộ thread | Chỉ chặn goroutine đó |
Kết luận: Goroutines giúp tối ưu hóa việc xử lý đồng thời mà không cần tạo quá nhiều OS threads, giúp giảm chi phí tài nguyên và tăng hiệu suất chương trình. 🚀

- Goroutine không chạy trực tiếp trên CPU: Câu hỏi ban đầu làm nổi bật một quan niệm sai lầm phổ biến. Mặc dù tất cả code cuối cùng đều cần thời gian CPU, Goroutine là một abstraction (trừu tượng). Chúng được quản lý bởi Go runtime (môi trường thời gian chạy của Go).
- OS thread là ngữ cảnh thực thi: OS thread là thực thể mà OS scheduler (bộ lập lịch của hệ điều hành) hiểu và có thể cấp phát thời gian CPU cho nó. Hãy coi nó như một “công nhân vật lý”. Một Goroutine cần một OS thread để thực sự chạy. Nó không thể thực thi một mình.
- Vai trò của Go runtime: Go runtime nằm giữa các Goroutine và các OS thread. Nó chịu trách nhiệm multiplexing (chuyển đổi) nhiều Goroutine trên một số lượng nhỏ hơn các OS thread. Đây là điều khiến Goroutine trở nên nhẹ. Runtime quản lý sự phức tạp của việc lập lịch và chuyển đổi ngữ cảnh.
- Quản lý stack là chìa khóa: OS thread thường có kích thước stack lớn (ví dụ: 2MB). Điều này là do hệ điều hành cần lưu trữ nhiều thông tin trạng thái cho mỗi thread. Goroutine, được quản lý bởi Go runtime, có stack nhỏ hơn nhiều (ví dụ: ban đầu là 2KB). Go runtime quản lý các stack này và có thể tăng hoặc giảm chúng khi cần. Việc quản lý stack hiệu quả này là một đóng góp quan trọng vào lợi ích hiệu suất của Goroutine.
- Mối liên hệ: OS scheduler lập lịch các OS thread. Go runtime scheduler quản lý các Goroutine và gán chúng cho các OS thread có sẵn. Một OS thread duy nhất có thể thực thi nhiều Goroutine theo thời gian, nhờ khả năng multiplexing của Go runtime.
- Câu hỏi về stack: Cuộc thảo luận về stack làm nổi bật một sự khác biệt quan trọng. Hệ điều hành quản lý stack của OS thread. Go runtime quản lý stack của Goroutine. Chúng liên quan nhau ở chỗ stack của Goroutine nằm trong không gian bộ nhớ của OS thread mà nó hiện đang chạy. Tuy nhiên, Go runtime có các cơ chế riêng để quản lý và tăng/giảm các stack Goroutine này.
Gợi ý cho nghiên cứu sâu hơn:
- “Go scheduler”: Tìm kiếm thông tin về cách Go scheduler hoạt động. Hiểu rõ vai trò của nó là rất quan trọng.
- “M:N threading”: Cách tiếp cận của Go thường được mô tả là mô hình M:N threading, trong đó M Goroutine được multiplexing trên N OS thread. Nghiên cứu khái niệm này.
- “Goroutine stack management”: Tìm hiểu cách Go runtime quản lý stack của Goroutine. Đây là một khía cạnh quan trọng của hiệu quả của nó.
- “Context switching”: Tìm hiểu cách chuyển đổi ngữ cảnh hoạt động ở cả cấp độ OS thread và cấp độ Goroutine. Điều này sẽ giúp bạn hiểu tại sao Goroutine lại nhanh hơn rất nhiều khi chuyển đổi giữa chúng.

- Goroutine cần một Thread của HĐH để chạy
- Một Goroutine không thể chạy trực tiếp trên CPU mà cần một luồng của hệ điều hành (OS thread) để làm ngữ cảnh thực thi.
- Mô hình phân cấp:
CPU → Tiến trình (Process) → Luồng HĐH (OS Thread) → Go Runtime → Goroutine - Bộ runtime của Go quản lý Goroutine và ánh xạ chúng lên OS Threads một cách hiệu quả.
- So sánh Stack của Goroutine và OS Thread
- Luồng của HĐH có stack lớn (~2MB mỗi luồng).
- Goroutine có stack rất nhỏ (~2KB ban đầu) và có thể mở rộng nếu cần.
- Bộ runtime của Go quản lý stack của Goroutine riêng biệt, giúp tiết kiệm bộ nhớ hơn so với cách sử dụng thread truyền thống.
- Bộ lập lịch của Go (Go Scheduler)
- Ánh xạ nhiều Goroutine vào một số ít OS Threads để tối ưu hóa tài nguyên.
- Hỗ trợ lập lịch mà không cần phụ thuộc hoàn toàn vào hệ điều hành, giúp tăng hiệu suất và giảm chi phí tạo thread mới.