Trong các chương trình Go sử dụng goroutines, việc sử dụng biến toàn cục (global variables) thường không được khuyến khích và có thể dẫn đến các vấn đề liên quan đến đồng bộ hóa và tính an toàn của dữ liệu. Dưới đây là lý do tại sao bạn nên cẩn thận khi sử dụng biến toàn cục trong các chương trình sử dụng goroutines:
Goroutines thực hiện các tác vụ song song và có thể truy cập, thay đổi các biến toàn cục cùng một lúc. Nếu không có sự đồng bộ hóa hợp lý, điều này có thể gây ra các vấn đề về race condition (điều kiện tranh chấp), trong đó nhiều goroutine có thể truy cập và thay đổi biến toàn cục đồng thời, dẫn đến kết quả không xác định hoặc lỗi chương trình.
Ví dụ về race condition khi sử dụng biến toàn cục:
package main import ( "fmt" "time" ) var counter int // Biến toàn cục func increment() { for i := 0; i < 1000; i++ { counter++ } } func main() { go increment() go increment() time.Sleep(time.Second) // Đợi goroutines chạy xong fmt.Println("Giá trị của counter:", counter) }
Trong ví dụ này, kết quả của counter
có thể không phải là 2000 như mong đợi, do cả hai goroutines đang cố gắng tăng giá trị của counter
cùng lúc, gây ra race condition.
go run -race
Go cung cấp một công cụ giúp phát hiện các điều kiện race condition. Bạn có thể chạy chương trình Go với flag -race
để kiểm tra liệu có xảy ra race condition hay không:
go run -race main.go
Nếu phát hiện race condition, công cụ này sẽ in ra cảnh báo kèm theo thông tin về vị trí vấn đề xảy ra.
Để tránh các vấn đề liên quan đến biến toàn cục khi sử dụng goroutines, bạn nên sử dụng các cơ chế đồng bộ hóa hoặc truyền dữ liệu an toàn qua các kênh (channels).
sync.Mutex
Một cách để đảm bảo rằng chỉ một goroutine có thể truy cập và thay đổi biến toàn cục tại một thời điểm là sử dụng mutex từ gói sync
.
Ví dụ sử dụng sync.Mutex
:
package main import ( "fmt" "sync" "time" ) var counter int // Biến toàn cục var mutex sync.Mutex func increment() { for i := 0; i < 1000; i++ { mutex.Lock() // Đảm bảo chỉ có một goroutine truy cập biến counter counter++ mutex.Unlock() // Mở khóa để các goroutine khác có thể truy cập } } func main() { go increment() go increment() time.Sleep(time.Second) // Đợi goroutines chạy xong fmt.Println("Giá trị của counter:", counter) }
Trong ví dụ này, mutex.Lock()
đảm bảo rằng chỉ một goroutine có quyền truy cập vào biến counter
tại một thời điểm, ngăn chặn race condition.
Một cách khác để tránh sử dụng biến toàn cục và đồng bộ hóa là sử dụng channels để truyền dữ liệu giữa các goroutines. Channels giúp đảm bảo tính an toàn của dữ liệu mà không cần dùng đến biến toàn cục.
Ví dụ sử dụng channels:
package main import ( "fmt" "time" ) func increment(ch chan int) { counter := 0 for i := 0; i < 1000; i++ { counter++ } ch <- counter // Gửi kết quả qua kênh } func main() { ch := make(chan int) go increment(ch) go increment(ch) counter1 := <-ch counter2 := <-ch total := counter1 + counter2 fmt.Println("Tổng giá trị của counter:", total) }
Trong ví dụ này, dữ liệu được truyền qua kênh giữa các goroutines, loại bỏ nhu cầu sử dụng biến toàn cục và đảm bảo tính an toàn của dữ liệu.
sync.Mutex
hoặc sync.RWMutex
để đảm bảo rằng chỉ một goroutine có thể truy cập biến tại một thời điểm.Sử dụng các giải pháp này sẽ giúp đảm bảo tính an toàn, rõ ràng, và dễ bảo trì cho chương trình của bạn khi làm việc với goroutines.