once
sync.Once là một công cụ đồng bộ hóa trong thư viện chuẩn của Go, dùng để đảm bảo một hàm chỉ được thực thi một lần trong môi trường đồng thời. Nó thường được sử dụng trong các kịch bản như khởi tạo lazy, khởi tạo tài nguyên toàn cục, v.v., đảm bảo một thao tác cụ thể chỉ được thực thi một lần, ngay cả khi có nhiều coroutine đồng thời thực thi.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var once sync.Once
for i := range 10 {
wg.Add(1)
go func() {
defer wg.Done()
once.Do(func() {
fmt.Println(i)
})
}()
}
wg.Wait()
}Trong code trên, tổng cộng có 10 coroutine, nhưng bất kể thế nào, chỉ có một coroutine sẽ thực thi fmt.Println(i) trong once.Do(). Cụ thể là coroutine nào sẽ in, phụ thuộc vào coroutine nào đến once.Do() đầu tiên.
Cấu trúc
type Once struct {
done atomic.Uint32
m Mutex
}Cấu trúc nội bộ của nó rất đơn giản
done, một giá trị nguyên tử, dùng để biểu thị có được thực thi qua chưam, mutex, dùng để chặn các coroutine khác muốn thực thi
Nguyên lý của nó là khóa trước khi thực thi, sau đó cập nhật done, sau khi thực thi xong thì mở khóa.
Do
func (o *Once) Do(f func()) {
if o.done.Load() == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done.Load() == 0 {
defer o.done.Store(1)
f()
}
}Code tổng thể rất đơn giản,流程 như sau:
- Trực tiếp load giá trị nguyên tử, nếu đã thực thi qua rồi thì trực tiếp trả về
- Chưa thực thi qua, thì thử giữ lock, ở đây có thể là nhiều coroutine đang cạnh tranh mutex
- Khi người chiến thắng thành công giữ lock, vẫn cần phán đoán giá trị
donemột lần nữa, vì sau khi bạn giữ lock, người khác có thể đã hoàn thành khóa-thực thi-mở khóa rồi, lúc này bạn vừa được đánh thức. - Thực thi hàm mục tiêu
- Cập nhật giá trị
done - Giải phóng lock
Tóm tắt
sync.Once là một công cụ đồng bộ hóa rất đơn giản và hiệu quả, nó đảm bảo một thao tác nào đó chỉ được thực thi một lần trong môi trường đồng thời, từ đó tránh được việc lặp lại công việc hoặc lãng phí tài nguyên.
