sync.Once is a useful package for all sorts of things, including lazy initalization. Let's explore.
So in this video, we're going to talk about the sync.Once package.
In concurrent programs, ensuring initialization happens exactly once can be challenging, but it can also be important for performance reasons, as well as for lazy initialization, which can be important if creating something is really expensive.
So let's see how we might do this, and then we can talk through some concrete examples of when this can be used. What is sync.Once
? It guarantees a piece of code is executed only once—no matter how many goroutines call it concurrently.
Under the hood, it's using synchronization to ensure your initialization logic is effectively thread-safe. It can be really useful for singleton patterns, lazy initialization, and maybe one-time configuration setup. Let's see how to do this in Go.
We've got multiple goroutines each calling once.Do(initialize)
. Despite multiple calls, the initialize
function executes only once, and all goroutines will see the same initial state after initialization. If we run this, you'll see a goroutine call once, and after initialization is complete, we see the same value synchronized across all the goroutines. Before initialization, they saw different values; once initialization happened, they all saw the same thing.
One really powerful way to use this is in a singleton pattern. For example, we have a configuration object for a database. We only create it (load from a file or environment variable) the first time we call getConfig
. If it's never called, we never pay that cost. The same idea applies to creating a logger or initializing Stripe: you only do it when you need it, so you don't pay the initialization cost prematurely.
This pattern helps avoid extra overhead if you don't end up using that resource, and it ensures thread-safe, one-time setup across multiple goroutines. That’s essentially how sync.Once works and why it's so handy in certain Go applications.