Skip to content

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.

go
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

go
type Once struct {
    done atomic.Uint32
    m    Mutex
}

Cấu trúc nội bộ của nó rất đơn giản

  1. done, một giá trị nguyên tử, dùng để biểu thị có được thực thi qua chưa
  2. m, 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

go
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:

  1. 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ề
  2. Chưa thực thi qua, thì thử giữ lock, ở đây có thể là nhiều coroutine đang cạnh tranh mutex
  3. Khi người chiến thắng thành công giữ lock, vẫn cần phán đoán giá trị done mộ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.
  4. Thực thi hàm mục tiêu
  5. Cập nhật giá trị done
  6. 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.

Golang by www.golangdev.cn edit