Table of Contents
- Introduction
- Prerequisites
- Overview
- Using the Once Function
- Using the Do Function
- Real-World Example
- Recap
Introduction
In Go programming language, the sync package provides various synchronization primitives to simplify concurrent programming. Two such important functions are Once and Do. The Once function ensures that a given piece of code is executed only once regardless of the number of goroutines calling it. On the other hand, the Do function allows executing a function only if it has not been executed before. In this tutorial, we will explore these functions and understand how to use them effectively.
By the end of this tutorial, you will:
- Understand the purpose and benefits of using
OnceandDofunctions - Know how to utilize the
Oncefunction to execute code only once - Know how to utilize the
Dofunction to execute code if it has not been executed before - Be able to apply these concepts in real-world scenarios
Prerequisites
To follow along with this tutorial, you should have a basic understanding of Go programming language, including goroutines and concurrency. Make sure Go is properly installed on your system.
Overview
Concurrency in Go brings challenges when multiple goroutines need to access shared resources or execute critical sections of code. The sync package provides atomic operations and synchronization primitives such as Mutex and WaitGroup to address these challenges. The Once and Do functions offer additional control by ensuring certain code portions are executed in a specific way.
Using the Once Function
The Once function ensures a specific code block is executed only once regardless of the number of goroutines calling it. It is particularly useful when initializing a resource that should be created only once. Here’s the basic syntax of Once:
var once sync.Once
func doOnce() {
once.Do(func() {
// code to be executed only once
})
}
Let’s break down the code:
- We declare a variable
onceof typesync.Once. This variable will keep track of the execution state. - In the
doOncefunction, we callonce.Do()passing a function as an argument. The code inside this function block will be executed only once, even ifdoOnceis invoked by multiple goroutines simultaneously.
Note: The code inside the Do function will block until it completes. Hence, make sure to avoid any long-running or blocking operations to prevent other goroutines from being blocked.
Using the Do Function
The Do function allows executing a function only if it has not been executed before. This function can be used when you want to perform a specific action once, but not necessarily restrict other goroutines from executing it simultaneously. Here’s the basic syntax of Do:
var done bool
var mu sync.Mutex
func doIfNotDone() {
mu.Lock()
defer mu.Unlock()
if !done {
// code to be executed if not done before
done = true
}
}
Let’s analyze the code:
- We declare a boolean variable
doneto track whether the code has been executed before. - A
sync.Mutexis used to synchronize access to thedonevariable. - In the
doIfNotDonefunction, we first acquire the lock usingmu.Lock()and then release it usingdefer mu.Unlock(). This ensures that only one goroutine can check/modifydoneat a time. - Inside the function, we check if
doneis false. If it is, we execute the code and setdoneto true. Subsequent invocations will seedoneastrueand skip the code.
Note: The sync.Mutex approach can be useful when you need a more fine-grained control over execution and want to allow concurrent execution up to a certain point.
Real-World Example
Let’s consider a real-world example where the Once and Do functions can be applied.
var (
initDBOnce sync.Once
db *sql.DB
dbErr error
)
func initializeDB() {
initDBOnce.Do(func() {
db, dbErr = sql.Open("mysql", "user:password@tcp(localhost:3306)/db")
})
}
func serveHTTP() {
// ...
initializeDB()
// ...
}
Here’s what the code does:
- We declare a
sync.OncevariableinitDBOnceto ensure the database is initialized only once. - The
initializeDBfunction usesinitDBOnce.Do()to check if initialization has already been done. If not, it opens a connection to a MySQL database. - In the
serveHTTPfunction (e.g., an HTTP handler), we callinitializeDBto ensure the database is initialized before serving any requests.
With this pattern, regardless of the number of goroutines calling serveHTTP, the database will be initialized only once due to the Once guarantee.
Recap
In this tutorial, we explored the Once and Do functions in the sync package of Go programming language. We learned to utilize the Once function to ensure a given code block is executed only once, regardless of concurrent calls. We also understood how to use the Do function to execute code only if it has not been executed before. Additionally, we saw a real-world example where these functions can be applied.
Understanding and effectively using these functions can significantly simplify concurrent programming in Go while ensuring correctness and performance. Experiment with different scenarios to gain more familiarity with Once and Do functions.
Remember, leveraging the power of Go’s concurrency features requires careful design and synchronization. With the sync package and its functions like Once and Do, you can better control the execution of critical sections in concurrent programs.
Happy coding!