When writing concurrent programs in Go, one common challenge developers face is ensuring that certain initialization tasks only happen once. sync.Once from Go's standard library is a powerful tool for addressing this challenge. It ensures that a piece of code is executed only once, even if it's being called from multiple goroutines.
Understanding sync.Once
The sync.Once type provides a method called Do. This method is responsible for ensuring that a specified function is executed just once throughout the lifetime of the program, regardless of how many times Do is called or from how many goroutines it is invoked.
Example of Using sync.Once
package main
import (
"fmt"
"sync"
)
var once sync.Once
func initialize() {
fmt.Println("Initialization function executed")
}
func main() {
for i := 0; i < 3; i++ {
go func(i int) {
once.Do(initialize)
fmt.Printf("Goroutine %d finished\n", i)
}(i)
}
// Wait for user input to prevent program from exiting early
fmt.Scanln()
}
In the example above, the initialize function will only print "Initialization function executed" once, even though the surrounding loop causes once.Do(initialize) to be called three times in separate goroutines. This guarantees that the initialization code runs exactly once in a concurrent setting, protecting the shared resource initialization.
Key Properties of sync.Once
- Thread-Safe:
sync.Oncecan be safely used by multiple goroutines, which eliminates the need for additional synchronization. - No Re-init: Once the function is executed, no further calls to
Dowill execute it again. Repeated calls toDowill have no effect. - Blocking: If multiple goroutines simultaneously invoke
Do, only one will run the function, and the others will block until the function has returned.
Applications of sync.Once
sync.Once is commonly used for:
- Singleton Pattern: Guaranteeing that a particular piece of initialization code is executed only once.
- Resource Initialization: Such as opening a network connection, initializing a library, or loading a configuration file.
- JavaScript and Golang Interoping: Efficiently bridging between multiple languages by making a certain function call or data load occur once.
Considerations When Using sync.Once
Be mindful that:
- If the function passed to
Dopanics, the state ofsync.Oncebecomes permanent. That meansDowon't be retried unless you explicitly handle panics within the initialization function. - Ensuring that
sync.Onceis appropriate for the task is crucial, as it's strictly for guaranteeing a one-time execution with implicit lock handling.
Understanding and employing sync.Once effectively can simplify concurrent program design, leading to safer execution patterns and remaining vigilant against inadvertent resource re-initialization.