Bài học theo mô-đun (4/5)
`sync.Mutex` và `sync.WaitGroup`
Kênh là thành ngữ chính của Go, nhưng đôi khi mang tính truyền thống hơn
nguyên thủy là cần thiết. Gói sync cung cấp mutexes, hãy đợi
các nhóm và các tiện ích khác để phối hợp trạng thái chia sẻ.
sync.Mutex: khóa độc quyền
import "sync"
type Counter struct {
mu sync.Mutex
count int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
func (c *Counter) Get() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}Mỗi lần chỉ một goroutine đi vào giữa Lock() và Unlock().
Mẫu bắt buộc: defer mu.Unlock() ngay sau Lock().
sync.RWMutex: nhiều độc giả, một nhà văn
Đối với trạng thái đọc nặng:
var mu sync.RWMutex
mu.RLock() // read lock: più goroutine simultanee OK
v := data
mu.RUnlock()
mu.Lock() // write lock: esclusivo
data = newValue
mu.Unlock()Chỉ hữu ích nếu số lần đọc thực sự chiếm ưu thế so với số lần ghi; nếu không thì thường xuyên
sync.Mutex đơn giản hơn và thường nhanh hơn nhờ chi phí nội bộ thấp hơn
sự tranh chấp.
sync.WaitGroup: đợi N goroutines
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
work(id)
}(i)
}
wg.Wait() // blocca finché tutti i Done() sono arrivatiQuy trình làm việc:
wg.Add(N)tăng bộ đếm lên N (trước khi khởi chạy goroutines).- Mỗi goroutine gọi
wg.Done()khi nó kết thúc (lý tưởng nhất là thông quadefer). wg.Wait()chặn cho đến khi bộ đếm quay về 0.
sync.Once: khởi tạo lười biếng an toàn theo luồng
var (
once sync.Once
config *Config
)
func GetConfig() *Config {
once.Do(func() {
config = loadConfig()
})
return config
}Lệnh gọi lại trong once.Do chạy chính xác một lần, ngay cả trong điều kiện đồng thời
cuộc gọi. Mẫu khởi tạo Singleton/lazy.
Mutex vs kênh: khi nào nên sử dụng cái gì
Nguyên tắc (cũng từ Câu hỏi thường gặp về Go chính thức):
| Trường hợp | Công cụ ưa thích |
|---|---|
| Chuyển quyền sở hữu dữ liệu | kênh |
| Phân phối công việc (hàng đợi công việc) | kênh |
| Phối hợp các goroutines độc lập | kênh |
| Bảo vệ trường cấu trúc | Tắt tiếng |
| Bộ nhớ đệm / bộ đếm | Mutex hoặc nguyên tử |
| Tài liệu tham khảo Singleton | sync.Once |
"Các kênh để sắp xếp, các mutex cho dữ liệu được chia sẻ" là một nguyên tắc chung.
Máy dò cuộc đua
Chạy thử nghiệm với -race để phát hiện các cuộc đua dữ liệu:
go test -race ./...
go run -race main.goBộ phát hiện cuộc đua thiết lập hệ nhị phân và ghi lại mọi thông tin chưa được đồng bộ hóa truy cập vào bộ nhớ dùng chung. Đây là công cụ số 1 để xác thực mã đồng thời trước khi sản xuất.
Hãy thử nó
Bảo vệ số đếm tăng dần bằng mu.Lock() + defer mu.Unlock().
Hiển thị gợi ý
trì hoãn mu.Unlock() ngay sau Lock() đảm bảo giải phóng ngay cả khi hoảng loạn.
Giải pháp khả dụng sau 3 lần thử
Khởi chạy 3 goroutine và đợi tất cả chúng sử dụng sync.WaitGroup (Add(3), Done() trong mỗi goroutine, Wait()).
Hiển thị gợi ý
Thêm TRƯỚC KHI khởi chạy goroutine; Hoàn thành BÊN TRONG goroutine với defer.
Giải pháp khả dụng sau 3 lần thử
Mẫu được đề xuất để đảm bảo mutex được phát hành là gì?
mu.Lock()
// ?Tóm tắt lại
sync.Mutexbảo vệ trạng thái chia sẻ; luôndefer mu.Unlock().- Sử dụng bộ nhận con trỏ để tránh sao chép mutex.
sync.RWMutexdành cho mô hình nhiều người đọc/một người viết (chỉ khi điều đó có ích).sync.WaitGroup:Addtrướcgo,Donevớidefer,Waitđể chờ.sync.Once: khởi tạo lười biếng an toàn theo luồng.- "Kênh phối hợp, mutexes cho dữ liệu được chia sẻ".
go test -race: công cụ số 1 để phát hiện các cuộc đua dữ liệu.