In projects that use Golang for back-end development, we usually need to declare some configuration classes or service singletons that are at the lower level of business logic. In order to save memory or cold start overhead, we usually use lazy-load to initialize these instances. The behavior of initializing singleton is a very classic case of concurrent processing. For example, in Java, we may use the method of establishing a double lock + volatile to ensure that the initialization logic is only accessed once, and that all threads can finally read the instance product of the initialization completed. This classic code can be written as follows:
public class Singleton { private volatile static Singleton uniqueSingleton; private Singleton() { } public Singleton getInstance() { if (null == uniqueSingleton) { synchronized () { if (null == uniqueSingleton) { uniqueSingleton = new Singleton(); } } } return uniqueSingleton; } }
But in Golang, the way to implement lazy loading can be much simpler, and it can be satisfied with the built-in ones. Suppose we have a user singleton that needs to be read and printed by 1000 threads, and we can write it like this:
type User struct { Name string `json:"name"` Age int `json:"age"` } var user *User var userOnce func initUser() { user = &User{} cfgStr := `{"name":"foobar","age":18}` if err := ([]byte(cfgStr), user); err != nil { panic("load user err: " + ()) } } func getUser() *User { (initUser) return user } func TestSyncOnce(t *) { var wg for i := 1; i < 1000; i++ { (1) go func(n int) { defer () curUser := getUser() ("[%d] got user: %+v", n, curUser) }(i) } () }
In this code, we first declare an instance through var userOnce, and then in getUser, we declare (initUser) this operation. Suppose that a goroutine arrives first, it will lock and execute initUser. After other goroutines arrive, you have to wait until the first goroutine has completed the initUser before you continue to return the user. In this way, it can ensure that initUser will only be executed once, and the second is that all goroutines can finally read the user singleton that is initialized.
The working mechanism is also very simple, and can be achieved through a lock and a flag:
func (o *Once) Do(f func()) { if atomic.LoadUint32(&) == 0 { // If it is 1, it means it has been completed, skip (f) } } func (o *Once) doSlow(f func()) { () // Only 1 goroutine can get the lock, and the rest are waiting defer () if == 0 { // If 0 is still the first one, it means that the goroutine has been completed. defer atomic.StoreUint32(&, 1) f() } }
Finally, you need to note that there is a pit point on it. You cannot and do nil judgments for singletons in advance like Java. For example, the following code is problematic:
func initUser() { user = &User{} // Give a zero-value instance first cfgStr := `{"name":"foobar","age":18}` // Then load the json content and complete the initialization if err := ([]byte(cfgStr), user); err != nil { panic("load user err: " + ()) } } func getUser() *User { if user == nil { (initUser) } return user }
Since Golang does not have the volatile keyword, it cannot control the visibility of singletons in memory. When many goroutines are concurrent, such execution timing may occur:
- goroutine-A After passing the user == nil judgment of getUser, enters the initUser logic, and goes to the line cfgStr := XXX
- At this time, switch to goroutine-B. Because goroutine-A has passed the user = &User{} line in initUser, so the user == nil judgment is skipped, and the user instance that has not been completely initialized is returned, and then it continues to run, and it does not switch back to goroutine-A
This result leads to goroutine running after getting an instance that has not been initialized, and there will be problems later. Therefore, you need to pay attention in actual combat. When using it, you cannot and do not need to add these nil judgments to satisfy logic such as lazy loading singletons/configuration.
This is the introduction to this article about the usage and pitfalls of golang to implement lazy loading. For more related go to lazy loading content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!