验证码: 看不清楚,换一张 查询 注册会员,免验证
  • {{ basic.site_slogan }}
  • 打开微信扫一扫,
    您还可以在这里找到我们哟

    关注我们

go sync.Once如何实现高效单例模式

阅读:582 来源:乙速云 作者:代码code

go sync.Once如何实现高效单例模式

      基本实现

      1 单例模式定义

      单例模式是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点来访问这个实例。在整个应用程序中,所有对于这个类的访问都将返回同一个实例对象。

      2 sync.Once实现单例模式

      下面是一个简单的示例代码,使用 sync.Once 实现单例模式:

       package singleton
       import "sync"
       type singleton struct {
           // 单例对象的状态
       }
       var (
           instance *singleton
           once     sync.Once
       )
       func GetInstance() *singleton {
           once.Do(func() {
               instance = &singleton{}
               // 初始化单例对象的状态
           })
           return instance
       }

      在上面的示例代码中,我们定义了一个 singleton 结构体表示单例对象的状态,然后将它的实例作为一个包级别的变量 instance,并使用一个 once 变量来保证 GetInstance 函数只被执行一次。

      GetInstance 函数中,我们使用 once.Do 方法来执行一个初始化单例对象。由于 once.Do 方法是基于原子操作实现的,因此可以保证并发安全,即使有多个协程同时调用 GetInstance 函数,最终也只会创建一个对象。

      3 其他方式实现单例模式

      3.1 全局变量定义时赋值,实现单例模式

      在 Go 语言中,全局变量会在程序启动时自动初始化。因此,如果在定义全局变量时给它赋值,则对象的创建也会在程序启动时完成,可以通过此来实现单例模式,以下是一个示例代码:

      type MySingleton struct {
          // 字段定义
      }
      var mySingletonInstance = &MySingleton{
          // 初始化字段
      }
      func GetMySingletonInstance() *MySingleton {
          return mySingletonInstance
      }

      在上面的代码中,我们定义了一个全局变量 mySingletonInstance 并在定义时进行了赋值,从而在程序启动时完成了对象的创建和初始化。在 GetMySingletonInstance 函数中,我们可以直接返回全局变量 mySingletonInstance,从而实现单例模式。

      3.2 init 函数实现单例模式

      在 Go 语言中,我们可以使用 init 函数来实现单例模式。init 函数是在包被加载时自动执行的函数,因此我们可以在其中创建并初始化单例对象,从而保证在程序启动时就完成对象的创建。以下是一个示例代码:

      package main
      type MySingleton struct {
          // 字段定义
      }
      var mySingletonInstance *MySingleton
      func init() {
          mySingletonInstance = &MySingleton{
              // 初始化字段
          }
      }
      func GetMySingletonInstance() *MySingleton {
          return mySingletonInstance
      }

      在上面的代码中,我们定义了一个包级别的全局变量 mySingletonInstance,并在 init 函数中创建并初始化了该对象。在 GetMySingletonInstance 函数中,我们直接返回该全局变量,从而实现单例模式。

      3.3 使用互斥锁实现单例模式

      在 Go 语言中,可以只使用一个互斥锁来实现单例模式。下面是一个简单代码的演示:

      var instance *MySingleton
      var mu sync.Mutex
      func GetMySingletonInstance() *MySingleton {
         mu.Lock()
         defer mu.Unlock()
         if instance == nil {
            instance = &MySingleton{
               // 初始化字段
            }
         }
         return instance
      }

      在上面的代码中,我们使用了一个全局变量instance来存储单例对象,并使用了一个互斥锁 mu 来保证对象的创建和初始化。具体地,我们在 GetMySingletonInstance 函数中首先加锁,然后判断 instance 是否已经被创建,如果未被创建,则创建并初始化对象。最后,我们释放锁并返回单例对象。

      需要注意的是,在并发高的情况下,使用一个互斥锁来实现单例模式可能会导致性能问题。因为在一个 goroutine 获得锁并创建对象时,其他的 goroutine 都需要等待,这可能会导致程序变慢。

      4 使用sync.Once实现单例模式的优点

      相对于init 方法和使用全局变量定义赋值单例模式的实现,sync.Once 实现单例模式可以实现延迟初始化,即在第一次使用单例对象时才进行创建和初始化。这可以避免在程序启动时就进行对象的创建和初始化,以及可能造成的资源的浪费。

      而相对于使用互斥锁实现单例模式,使用 sync.Once 实现单例模式的优点在于更为简单和高效。sync.Once提供了一个简单的接口,只需要传递一个初始化函数即可。相比互斥锁实现方式需要手动处理锁、判断等操作,使用起来更加方便。而且使用互斥锁实现单例模式需要在每次访问单例对象时进行加锁和解锁操作,这会增加额外的开销。而使用 sync.Once 实现单例模式则可以避免这些开销,只需要在第一次访问单例对象时进行一次初始化操作即可。

      但是也不是说sync.Once便适合所有的场景,这个是需要具体情况具体分析的。下面说明sync.Onceinit方法,在哪些场景下使用init更好,在哪些场景下使用sync.Once更好。

      5 sync.Once和init方法适用场景

      对于init实现单例,比较适用于在程序启动时就需要初始化变量的场景。因为init函数是在程序运行前执行的,可以确保变量在程序运行时已经被初始化。

      对于需要延迟初始化某些对象,对象被创建出来并不会被马上使用,或者可能用不到,例如创建数据库连接池等。这时候使用sync.Once就非常合适。它可以保证对象只被初始化一次,并且在需要使用时才会被创建,避免不必要的资源浪费。

      gin中单例模式的使用

      1 背景

      这里首先需要介绍下gin.Engine, gin.Engine是Gin框架的核心组件,负责处理HTTP请求,路由请求到对应的处理器,处理器可以是中间件、控制器或处理HTTP响应等。每个gin.Engine实例都拥有自己的路由表、中间件栈和其他配置项,通过调用其方法可以注册路由、中间件、处理函数等。

      一个HTTP服务器,只会存在一个对应的gin.Engine实例,其保存了路由映射规则等内容。

      为了简化开发者Gin框架的使用,不需要用户创建gin.Engine实例,便能够完成路由的注册等操作,提高代码的可读性和可维护性,避免重复代码的出现。这里对于一些常用的功能,抽取出一些函数来使用,函数签名如下:

      // ginS/gins.go
      // 加载HTML模版文件
      func LoadHTMLGlob(pattern string) {}
      // 注册POST请求处理器
      func POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {}
      // 注册GET请求处理器
      func GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {}
      // 启动一个HTTP服务器
      func Run(addr ...string) (err error) {}
      // 等等...

      接下来需要对这些函数来进行实现。

      2 具体实现

      首先从使用出发,这里使用POST方法/GET方法注册请求处理器,然后使用Run方法启动服务器:

      func main() {
         // 注册url对应的处理器
         POST("/login", func(c *gin.Context) {})
         // 注册url对应的处理器
         GET("/hello", func(c *gin.Context) {})
         // 启动服务
         Run(":8080")
      }

      这里我们想要的效果,应该是调用Run方法启动服务后,往/login路径发送请求,此时应该执行我们注册的对应处理器,往/hello路径发送请求也是同理。

      所以,这里POST方法,GET方法,Run方法应该都是对同一个gin.Engine 进行操作的,而不是各自使用各自的gin.Engine实例,亦或者每次调用就创建一个gin.Engine实例。这样子才能达到我们预想的效果。

      所以,我们需要实现一个方法,获取gin.Engine实例,每次调用该方法都是获取到同一个实例,这个其实也就是单例的定义。然后POST方法,GET方法又或者是Run方法,调用该方法获取到gin.Engine实例,然后调用实例去调用对应的方法,完成url处理器的注册或者是服务的启动。这样子就能够保证是使用同一个gin.Engine实例了。具体实现如下:

      // ginS/gins.go
      import (
         "github.com/gin-gonic/gin"
      )
      var once sync.Once
      var internalEngine *gin.Engine
      func engine() *gin.Engine {
         once.Do(func() {
            internalEngine = gin.Default()
         })
         return internalEngine
      }
      // POST is a shortcut for router.Handle("POST", path, handle)
      func POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
         return engine().POST(relativePath, handlers...)
      }
      // GET is a shortcut for router.Handle("GET", path, handle)
      func GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
         return engine().GET(relativePath, handlers...)
      }

      这里engine() 方法使用了 sync.Once 实现单例模式,确保每次调用该方法返回的都是同一个 gin.Engine 实例。然后POST/GET/Run方法通过该方法获取到gin.Engine实例,然后调用实例中对应的方法来完成对应的功能,从而达到POST/GET/Run等方法都是使用同一个实例操作的效果。

      3 sync.Once实现单例的好处

      这里想要达到的目的,其实是GET/POST/Run等抽取出来的函数,使用同一个gin.Engine实例。

      为了达到这个目的,我们其实可以在定义internalEngine 变量时,便对其进行赋值;或者是通init函数完成对internalEngine变量的赋值,其实都可以。

      但是我们抽取出来的函数,用户并不一定使用,定义时便初始化或者在init方法中便完成了对变量的赋值,用户没使用的话,创建出来的gin.Engine实例没有实际用途,造成了不必要的资源的浪费。

      而engine方法使用sync.Once实现了internalEngin的延迟初始化,只有在真正使用到internalEngine时,才会对其进行初始化,避免了不必要的资源的浪费。

      这里其实也印证了上面我们所说的sync.Once的适用场景,对于不会马上使用的单例对象,此时可以使用sync.Once来实现。

    分享到:
    *特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: hlamps#outlook.com (#换成@)。
    相关文章
    {{ v.title }}
    {{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
    你可能感兴趣
    推荐阅读 更多>