Go was designed from the ground up for concurrency. It provides simple yet powerful tools like goroutines, channels, and the select statement. Unlike most languages, concurrent programming in Go is a core part of the language design.
Go được thiết kế từ đầu cho lập trình đồng thời. Nó cung cấp các công cụ đơn giản nhưng mạnh mẽ như goroutine, channel, và select statement. Không giống như hầu hết các ngôn ngữ khác, lập trình đồng thời trong Go là một phần cốt lõi của thiết kế ngôn ngữ.
A goroutine is a lightweight thread managed by the Go runtime. Goroutines are much lighter than OS threads -> a goroutine costs only about 2KB of stack. The Go runtime uses M:N scheduling to map thousands of goroutines onto fewer OS threads. You start a goroutine with the go keyword.
Goroutine là một hàm được chạy đồng thời với các mã khác. Chúng nhẹ hơn nhiều so với OS thread → một goroutine chỉ tốn khoảng 2KB stack. Go runtime sử dụng M:N scheduling để ánh xạ hàng ngàn goroutine lên một số ít luồng OS. Bạn khởi chạy một goroutine bằng từ khóa go.
1package main23import (4 fmt "fmt"5 "time"6)78func main() {9 // Start a goroutine10 go func() {11 fmt.Println("Hello from goroutine")12 }()A channel allows goroutines to communicate safely by sending and receiving values. make(chan int) creates an unbuffered channel where send and receive block until both sides are ready. make(chan int, 10) creates a buffered channel with capacity 10. You send with ch -> v and receive with v := -> ch.
Channel cho phép goroutine giao tiếp an toàn với nhau bằng cách gửi và nhận giá trị. Hàm make(chan int) tạo một channel unbuffered, trong đó gửi và nhận block cho đến khi cả hai bên sẵn sàng. make(chan int, 10) tạo một channel buffered với dung lượng 10. Bạn gửi với ch → v và nhận với v := → ch.
1package main23import (4 "fmt"5 "time"6)78func main() {9 // Unbuffered channel10 ch := make(chan string)1112 go func() {Select lets a goroutine wait on multiple channels. It is similar to switch but for channels. Cases are evaluated in any order, and the first one ready executes. A default case runs if no other case is ready (non-blocking).
Select cho phép một goroutine chờ trên nhiều channel. Nó tương tự switch nhưng cho channel. Các case được đánh giá trong bất kỳ thứ tự nào, và case đầu tiên có sẵn được thực thi. Case default chạy nếu không có case khác sẵn sàng (non-blocking).
1package main23import (4 "fmt"5 "time"6)78func main() {9 ch1 := make(chan string)10 ch2 := make(chan string)1112 go func() {WaitGroup synchronizes a group of goroutines. Call wg.Add(n) to set the number of goroutines, wg.Done() to signal completion (usually defer it), and wg.Wait() to block until all goroutines are done. This is cleaner than adding delays with Sleep.
WaitGroup đồng bộ hóa một nhóm goroutine. Gọi wg.Add(n) để đặt số lượng goroutine, wg.Done() để báo hiệu hoàn thành (thường defer nó), và wg.Wait() để block cho đến khi tất cả goroutine xong. Đây là một cách sạch sẽ hơn thời gian để thêm delay như Sleep.
1package main23import (4 "fmt"5 "sync"6)78func main() {9 var wg sync.WaitGroup1011 // Fan-out: launch N goroutines12 for i := 1; i <= 3; i++ {Mutex protects shared data from corruption by allowing only one goroutine to access it at a time. Call mu.Lock() before accessing and mu.Unlock() after (or defer Unlock to ensure unlock even if panic). RWMutex allows multiple goroutines to read concurrently but exclusive write.
Mutex bảo vệ dữ liệu chung khỏi corruption bằng cách cho phép chỉ một goroutine truy cập tại một lúc. Gọi mu.Lock() trước khi truy cập và mu.Unlock() sau (hoặc defer Unlock để đảm bảo unlock ngay cả khi có panic). RWMutex cho phép nhiều goroutine đọc đồng thời nhưng độc quyền cho viết.
1package main23import (4 "fmt"5 "sync"6)78type Counter struct {9 mu sync.Mutex10 value int11}12The done channel pattern signals cancellation. Pipeline pattern chains goroutines: producer sends data, transformer processes it, consumer receives the result. Worker pool launches a fixed number of worker goroutines waiting on a jobs channel.
Done channel dùng để hủy bỏ (cancellation). Pipeline pattern kết nối goroutine: producer gửi dữ liệu, transformer xử lý, consumer nhận kết quả. Worker pool khởi tạo một số lượng cố định worker goroutine chờ trên job channel.
1package main23import (4 "fmt"5)67// Done channel for cancellation8func cancellationExample() {9 done := make(chan bool)10 ch := make(chan int)1112 go func() {Go includes a race detector. Run go run -race main.go to find data races. The race detector reports any unsynchronized access to the same memory from different goroutines. Always use the -race flag when testing concurrent code.
Go bao gồm một race detector (công cụ phát hiện race condition). Chạy go run -race main.go để tìm data race. Race detector báo cáo bất kỳ truy cập không đồng bộ nào vào cùng một bộ nhớ từ các goroutine khác nhau. Luôn sử dụng flag -race khi testing code đồng thời.
1// main.go2package main34import (5 "fmt"6)78// BAD: Data race - multiple goroutines access x without synchronization9func badExample() {10 var x int11 go func() {12 x = 1 // Write without syncChạy go test -race ./... để kiểm tra các test với race detector. Nếu race được phát hiện, test sẽ thất bại. Điều này là rất quan trọng để đảm bảo code đồng thời an toàn.
Key Takeaways
Điểm Chính
- Goroutines are lightweight and managed by the Go runtimeGoroutine nhẹ và được quản lý bởi Go runtime
- Channels enable safe communication between goroutinesChannel cho phép giao tiếp an toàn giữa các goroutine
- WaitGroup synchronizes goroutine completionWaitGroup đồng bộ hoàn thành của các goroutine
- Use -race flag to detect data races in testingSử dụng flag -race để phát hiện data race khi testing
Practice
Test your understanding of this chapter
How many OS threads does Go use by default for M:N scheduling?
Go sử dụng bao nhiêu OS thread theo mặc định cho M:N scheduling?
What is the main difference between an unbuffered and a buffered channel?
Sự khác biệt chính giữa unbuffered channel và buffered channel là gì?
The defer keyword with Unlock() is optional because Go automatically unlocks mutexes when a goroutine exits.
Từ khóa defer với Unlock() là tùy chọn vì Go tự động unlock mutex khi goroutine kết thúc.
A buffered channel with capacity 5 will block on send if it already contains 5 values.
Một buffered channel với dung lượng 5 sẽ block khi gửi nếu nó đã chứa 5 giá trị.
Launch a goroutine that prints a message
Khởi chạy một goroutine in ra một thông báo
func() {
fmt.Println("Hello from goroutine")
}()