Concurrency · 3 MIN READ
sync.Once runs a function one time, for everyone
Initialise shared state without a lock you have to remember.
Effective Go and the sync docs: Once.Do(f) runs f once, even when many goroutines call Do at the same time. After that, Do is a no-op. It is the right shape for a lazy setup that must not run twice — registering a driver, reading a config, seeding a generator.
Do not copy an Once after it has been used. Do not call Do with a different f and expect that second f to run. The once is on the Once, not on the function value.
SEE THE IDEA
Concurrent callers share one completed initialization
- 01
One shared Once
goonce.Do(initialize)The first caller runs the function
- 02
Caller A
goonce.Do(initialize)Waits if initialization is in progress
- 03
Caller B
goonce.Do(initialize)Observes the completed initialization
Do does not return to any caller until the one initialization call has finished.
In practice
Keep the Once next to the state it protects, and keep that state in one shared object. It is suitable for initialization that must run at most once. Use another design if an operation may fail and needs retries; Once records the attempt rather than guaranteeing successful initialization.
Watch out
If the function panics, Once still considers it done. Calling Do recursively on that same Once deadlocks.
Work through the exercise
package main
import "sync"
// Call runs fn through once. Two calls must run fn once.
func Call(once *sync.Once, fn func()) {
fn()
}
func main() {}Trace each value and write down your reasoning. Copy the source into GopherForge or your local Go environment to compile and check it. This web page does not execute your code or send it to a compiler service.
Progress stays in this browser. No account needed.