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
Once
andDo
functions - Know how to utilize the
Once
function to execute code only once - Know how to utilize the
Do
function 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
once
of typesync.Once
. This variable will keep track of the execution state. - In the
doOnce
function, we callonce.Do()
passing a function as an argument. The code inside this function block will be executed only once, even ifdoOnce
is 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
done
to track whether the code has been executed before. - A
sync.Mutex
is used to synchronize access to thedone
variable. - In the
doIfNotDone
function, we first acquire the lock usingmu.Lock()
and then release it usingdefer mu.Unlock()
. This ensures that only one goroutine can check/modifydone
at a time. - Inside the function, we check if
done
is false. If it is, we execute the code and setdone
to true. Subsequent invocations will seedone
astrue
and 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.Once
variableinitDBOnce
to ensure the database is initialized only once. - The
initializeDB
function usesinitDBOnce.Do()
to check if initialization has already been done. If not, it opens a connection to a MySQL database. - In the
serveHTTP
function (e.g., an HTTP handler), we callinitializeDB
to 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!