I want to use a global variable. During first access to read it, I want to write to this variable using an API. For any subsequent access to read it, it should not require locks.
This is the implementation I have written. But this seems like an overkill for such a small task.
- Is there a conventional way it is done in go?
- If I have multiple such global variables and I don't want to put them under this same struct, is there a way to do this without code duplication?
- Read this answer. One way for making single variable's use to be atomic is by using "sync/atomic" library. But, the functions are only there for integer types. How does community work with "string" type?
Please free to suggest any other unrelated changes.
PS: Using sync.Once didn't seem right. If the first time to fetch fails, the program will never get 'clusterName'. Also, couldn't think of a way to make other readers wait until sync.Once is complete. Any ideas?
package main
import (
"fmt"
"sync"
"time"
)
type ClusterName struct {
sync.RWMutex
clusterName string
}
// Global variable. Read-many, write once.
var clusterName ClusterName
// Method to avoid locking during reads if 'clusterName' has been filled once.
func GetClusterName() (string, error) {
// Take read lock to see if 'clusterName' is filled.
clusterName.RLock()
if clusterName.clusterName == "" {
// 'clusterName' is not filled. Release read-lock and call method to fill it with write-lock.
clusterName.RUnlock()
return getClusterName()
}
defer clusterName.RUnlock()
return clusterName.clusterName, nil
}
// Method to fetch and fill cluster name. Takes a write-lock.
func getClusterName() (string, error) {
// Take write-lock.
clusterName.Lock()
defer clusterName.Unlock()
// See if previous writer has already filled this. Just return if already filled.
if clusterName.clusterName != "" {
return clusterName.clusterName, nil
}
// Only 1 writer will ever reach here.
var err error
clusterName.clusterName, err = fetchClusterName()
if err != nil {
return "", err
}
return clusterName.clusterName, nil
}
func fetchClusterName() (string, error) {
// API call.
time.Sleep(time.Second)
return "test-cluster-name", nil
}
func main() {
for i := 0; i < 50; i++ {
fmt.Println(GetClusterName())
}
}
Playground: https://go.dev/play/p/PV42PMXliRC