Skip to content

Latest commit

 

History

History

04_once

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

🔂 sync.Once in Go

💡 What is sync.Once?

sync.Once is a synchronization primitive provided by the sync package in Go. It ensures that a particular piece of code is executed only once throughout the lifetime of the program, regardless of how many goroutines attempt to execute it.

⏩ When to Use sync.Once?

sync.Once is commonly used in scenarios like:

  • Lazy initialization.
  • Ensuring an action is executed only once, such as setting up configurations or opening database connections.

📖 How to Use sync.Once

sync.Once operates with a single method called Do(f func()). This method accepts a function and guarantees that it will be executed exactly once.

Syntax

var once sync.Once

once.Do(func() {
    // Code to execute once
})

🔍 Example Use Cases

1. Initialization Once

package main

import (
	"fmt"
	"sync"
)

var once sync.Once

func initialize() {
	fmt.Println("Initialization executed.")
}

func main() {
	for i := 0; i < 5; i++ {
		go once.Do(initialize)
	}

	// Wait for goroutines to finish
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		// This won't execute again
		once.Do(initialize)
	}()
	wg.Wait()
}

Output:

Initialization executed.

Explanation:

  • Even though multiple goroutines are calling once.Do(initialize), the function initialize is executed only once.

2. Singleton Pattern

sync.Once is well-suited for implementing the singleton pattern in Go, ensuring that a single instance is initialized.

package main

import (
	"fmt"
	"sync"
)

type Singleton struct {
	name string
}

var instance *Singleton
var once sync.Once

func GetInstance() *Singleton {
	once.Do(func() {
		instance = &Singleton{name: "MySingleton"}
		fmt.Println("Singleton instance created.")
	})
	return instance
}

func main() {
	for i := 0; i < 5; i++ {
		go func(id int) {
			s := GetInstance()
			fmt.Printf("Goroutine %d: Got instance: %v\n", id, s)
		}(i)
	}

	// Wait for goroutines to finish
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		GetInstance()
	}()
	wg.Wait()
}

Output:

Singleton instance created.
Goroutine 0: Got instance: &{MySingleton}
Goroutine 1: Got instance: &{MySingleton}
...

Explanation:

  • The instance is created only once and shared across all goroutines.

3. Ensuring Configuration Initialization

package main

import (
	"fmt"
	"sync"
)

var (
	config     map[string]string
	onceConfig sync.Once
)

func loadConfig() {
	fmt.Println("Loading configuration...")
	config = map[string]string{
		"host": "localhost",
		"port": "8080",
	}
}

func GetConfig() map[string]string {
	onceConfig.Do(loadConfig)
	return config
}

func main() {
	for i := 0; i < 5; i++ {
		go func(id int) {
			cfg := GetConfig()
			fmt.Printf("Goroutine %d: Config: %v\n", id, cfg)
		}(i)
	}

	// Wait for goroutines to finish
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		GetConfig()
	}()
	wg.Wait()
}

Output:

Loading configuration...
Goroutine 0: Config: map[host:localhost port:8080]
...

Explanation:

  • loadConfig is called only once, even though GetConfig is called multiple times from different goroutines.

⚠️ Notes on Using sync.Once

  1. The function passed to Do should not return an error:

    • sync.Once does not provide a mechanism to handle errors within the function that it calls.
    • If your function needs to return an error, you should handle it outside sync.Once.
  2. Avoid complex functions in Do:

    • The function executed by Do should only perform necessary tasks to avoid deadlocks or hard-to-debug errors.
  3. Cannot reset sync.Once:

    • After Do successfully executes, it cannot be "reset" to execute again. If you need to re-run the logic, you should consider other synchronization mechanisms.

📚 Summary

  • sync.Once ensures that a piece of code runs only once, helping you avoid issues related to multiple initializations or concurrent accesses.
  • It's especially useful for implementing patterns like singleton or lazy initialization.
  • Use it when you want to execute an action safely and exactly once, especially in a multi-threaded environment.