SoFunction
Updated on 2025-03-05

Go Detailed explanation of the efficient singleton mode

1. Introduction

This article describes the use of implementing singleton patterns, including the definition of singleton patterns, and examples of using singleton patterns to implement singleton patterns, while also comparing the implementation of other singleton patterns. Finally, we end with an example using an implementation of a singleton pattern in an open source framework.

2. Basic implementation

2.1 Singleton pattern definition

Singleton pattern is a creative design pattern that ensures that a class has only one instance and provides a global access point to access this instance. Throughout the application, all accesses to this class will return the same instance object.

2.2 Implementing singleton mode

Here is a simple example code usingImplement singleton mode:

 package singleton
 import "sync"
 type singleton struct {
     // The state of a singleton object }
 var (
     instance *singleton
     once     
 )
 func GetInstance() *singleton {
     (func() {
         instance = &singleton{}
         // Initialize the state of a singleton object     })
     return instance
 }

In the above example code, we define asingletonThe structure represents the state of a singleton object and then uses its instance as a package-level variableinstanceand use oneonceVariables to ensureGetInstanceThe function is executed only once.

existGetInstanceIn the function, we useMethod to execute an initialized singleton object. becauseThe method is implemented based on atomic operations, so concurrency security can be ensured, even if multiple coroutines are called simultaneously.GetInstanceFunctions will eventually create only one object.

2.3 Other ways to implement singleton mode

2.3.1 Assign values ​​when defining global variables to implement singleton mode

In Go, global variables are automatically initialized when the program starts. Therefore, if you assign a value to a global variable when defining it, the creation of the object will also be completed at the start of the program. This can be used to implement singleton mode. Here is a sample code:

type MySingleton struct {
    // Field definition}
var mySingletonInstance = &MySingleton{
    // Initialize the field}
func GetMySingletonInstance() *MySingleton {
    return mySingletonInstance
}

In the above code, we define a global variablemySingletonInstanceAnd assign values ​​at definition time, so that objects are created and initialized at program startup. existGetMySingletonInstanceIn the function, we can directly return global variablesmySingletonInstance, thereby implementing singleton mode.

2.3.2 The init function implements singleton mode

In Go, we can useinitFunctions to implement singleton pattern.initFunctions are functions that are automatically executed when the package is loaded, so we can create and initialize singleton objects in it, ensuring that the object is created at the start of the program. Here is a sample code:

package main
type MySingleton struct {
    // Field definition}
var mySingletonInstance *MySingleton
func init() {
    mySingletonInstance = &MySingleton{
        // Initialize the field    }
}
func GetMySingletonInstance() *MySingleton {
    return mySingletonInstance
}

In the above code, we define a package-level global variablemySingletonInstance, and ininitThe object is created and initialized in the function. existGetMySingletonInstanceIn the function, we directly return the global variable to implement the singleton pattern.

2.3.3 Implementing singleton mode using mutex locks

In Go, you can use only one mutex to implement singleton mode. Here is a demonstration of a simple code:

var instance *MySingleton
var mu 
func GetMySingletonInstance() *MySingleton {
   ()
   defer ()
   if instance == nil {
      instance = &MySingleton{
         // Initialize the field      }
   }
   return instance
}

In the above code, we use a global variableinstanceTo store singleton objects and use a mutex lockmuto ensure the creation and initialization of objects. Specifically, we areGetMySingletonInstanceFirst add lock in the function, then judgeinstanceWhether it has been created, and if it has not been created, create and initialize the object. Finally, we release the lock and return the singleton object.

It should be noted that using a mutex to implement singleton mode in case of high concurrency may cause performance problems. Because when a goroutine acquires a lock and creates an object, other goroutines need to wait, which may cause the program to slow down.

2.4 Advantages of using singleton mode

Relative toinitMethods and implementations of the assignment singleton pattern using global variables,Implementing singleton mode allows for delayed initialization, that is, creation and initialization are only done when a singleton object is used for the first time. This can avoid the creation and initialization of objects at the start of the program and the possible waste of resources.

Compared with using mutex to implement singleton mode,The advantage of implementing a singleton mode is that it is simpler and more efficient. Provides a simple interface, which only needs to pass an initialization function. Compared with the implementation method of mutex locks, it requires manual processing of locks, judgments and other operations, which is more convenient to use. Moreover, using mutex to implement singleton mode requires locking and unlocking operations every time a singleton object is accessed, which adds additional overhead. and useImplementing singleton mode can avoid these overheads, and only requires an initialization operation when accessing a singleton object for the first time.

But it's notIt is suitable for all scenarios, and this requires specific analysis of specific situations. The following descriptionandinitMethod, in which scenariosinitBetter, in which scenarios to usebetter.

2.5 and init method applicable scenarios

forinitImplementing a singleton is more suitable for scenarios where variables need to be initialized when the program starts. becauseinitFunctions are executed before the program runs, which ensures that variables have been initialized when the program runs.

For certain objects that need to be initialized late, the object is created and will not be used immediately, or may not be used, such as creating a database connection pool. Use this timeIt's very suitable. It ensures that the object is only initialized once and will be created when needed, avoiding unnecessary waste of resources.

3. Use of singleton mode in gin

3.1 Background

Here we need to introduce it first, It is the core component of the Gin framework, which is responsible for handling HTTP requests and routing requests to the corresponding processor. The processor can be a middleware, a controller, or a process HTTP response, etc. EachInstances all have their own routing tables, middleware stacks and other configuration items. By calling their methods, you can register routes, middleware, processing functions, etc.

An HTTP server will only have a corresponding oneinstance, which saves routing mapping rules and other contents.

In order to simplify the use of developers' Gin framework, no user creation is requiredInstances can complete route registration and other operations, improve the readability and maintainability of the code, and avoid the occurrence of duplicate code. Here, for some commonly used functions, some functions are extracted for use, and the function signature is as follows:

// ginS/
// Load HTML template filefunc LoadHTMLGlob(pattern string) {}
// Register POST request handlerfunc POST(relativePath string, handlers ...)  {}
// Register the GET request handlerfunc GET(relativePath string, handlers ...)  {}
// Start an HTTP serverfunc Run(addr ...string) (err error) {}
// etc...

These functions need to be implemented next.

3.2 Specific implementation

First, start from the use, here we use the POST method/GET method to register the request processor, and then use the Run method to start the server:

func main() {
   // Register the corresponding processor of the url   POST("/login", func(c *) {})
   // Register the corresponding processor of the url   GET("/hello", func(c *) {})
   // Start the service   Run(":8080")
}

The effect we want here should be to call the Run method and start the service./loginThe path sends a request, and the corresponding processor we registered should be executed at this time./helloThe same applies to path sending requests.

Therefore, here POST method, GET method, and Run method should all be the sameDo it, rather than using its ownInstance, or create one for each callExample. Only in this way can we achieve the expected results.

So, we need to implement a method to obtainInstance, each time this method is called, the same instance is obtained, which is actually the definition of a singleton. Then, the POST method, GET method or Run method is called to obtainInstance, then call the instance to call the corresponding method to complete the registration of the url processor or the startup of the service. This will ensure that the same one is usedAn example. The specific implementation is as follows:

// ginS/
import (
   "/gin-gonic/gin"
)
var once 
var internalEngine *
func engine() * {
   (func() {
      internalEngine = ()
   })
   return internalEngine
}
// POST is a shortcut for ("POST", path, handle)
func POST(relativePath string, handlers ...)  {
   return engine().POST(relativePath, handlers...)
}
// GET is a shortcut for ("GET", path, handle)
func GET(relativePath string, handlers ...)  {
   return engine().GET(relativePath, handlers...)
}

hereengine()Method usedImplement singleton mode to ensure that the same method is returned every time it is calledExample. Then the POST/GET/Run method is obtained through this methodInstance, then call the corresponding methods in the instance to complete the corresponding functions, so as to achieve the effect that POST/GET/Run and other methods are operated using the same instance.

3.3 Benefits of implementing singletons

The purpose to be achieved here is actually the function extracted from GET/POST/Run, etc., using the sameExample.

To achieve this, we can actually define itinternalEngineWhen a variable is assigned, it is assigned; orinitFunction completesinternalEngineAssigning variables is actually OK.

However, the functions we extracted are not necessarily used by the user, and are initialized when defined orinitThe assignment of variables is completed in the method. If the user does not use them, it will be created.The instance has no practical purpose and causes unnecessary waste of resources.

The engine method usesImplementedinternalEnginDelay initialization is only when it is actually usedinternalEngineIt will be initialized only when it is , avoiding unnecessary waste of resources.

This actually confirms what we said aboveFor applicable scenarios, for singleton objects that will not be used immediately, you can use it at this timeTo achieve it.

4. Summary

Singleton pattern is a commonly used design pattern that ensures that there is only one instance of a class. In singleton mode, singletons are often implemented using mutex locks or variable assignments. However, singletons can be implemented more easily using, while also avoiding unnecessary waste of resources. Of course, no implementation is suitable for all scenarios, and we need to analyze it according to the specific scenario.

The above is the detailed explanation of Go to achieve efficient singleton mode. For more information about singleton mode, please pay attention to my other related articles!