前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >利用golang优雅的实现单实例

利用golang优雅的实现单实例

作者头像
我的小碗汤
发布2018-08-22 11:18:02
2.6K0
发布2018-08-22 11:18:02
举报
文章被收录于专栏:我的小碗汤我的小碗汤

平时编写代码过程中,经常会遇到对于全局角度只需运行一次的代码,比如全局初始化操作,设计模式中的单例模式。针对单例模式,java中又出现了饿汉模式、懒汉模式,再配合synchronized同步关键字来实现。其目的无非就是将对象只初始化一次,而且最好保证在用到的时候再进行初始化,以避免初始化太早浪费资源,或者两次初始化破坏单例模式的实例唯一性。

Go语言的sync包中提供了一个Once类型来保证全局的唯一性操作,其通过Do(f func())方法来实现,即使 f 函数发生变化,其也不会被执行,下面我们来看一个小例子:

代码语言:javascript
复制
package main

import (
  "fmt"
  "sync"
  "time"
)

var once sync.Once

func main() {

  //once循环调用firstMethod函数10次,其实只执行一次
  for i := 0; i < 10; i++ {
    once.Do(firstMethod)
    fmt.Println("count:---", i)
  }

  //起10个协程,虽然用once去调secondMethod函数,但该函数不会被执行
  //只打印------i
  for i := 0; i < 10; i++ {
    go func(i int) {
      once.Do(secondMethod)
      fmt.Println("-----", i)
    }(i)
  }
  //主协程等1min,等上面的10个协程执行完
  time.Sleep(1 * time.Second)
}
func firstMethod() {
  fmt.Println("firstMethod")
}
func secondMethod() {
  fmt.Println("secondMethod")
}

运行程序输出如下结果:

代码语言:javascript
复制
firstMethod
count:--- 0
count:--- 1
count:--- 2
count:--- 3
count:--- 4
count:--- 5
count:--- 6
count:--- 7
count:--- 8
count:--- 9
----- 0
----- 2
----- 4
----- 5
----- 8
----- 6
----- 9
----- 3
----- 7
----- 1

Process finished with exit code 0

然后我们来分析一下:

程序中首先定义了一个名为oncesync.Once类型,然后main函数中第一个for循环10次,但是由于once.Do(f func)中的f函数全局只执行一次,所以firstMethod()函数只被执行一次;之后进入第二个for循环,这里once.Do(f func)方法的参数变为secondMethod函数。起10个协程去调,但由于once.Do(secondMethod)once.Do(firstMethod)用的是Once类型的同一个实例,所以secondMethod函数实际上不会被执行。这解释了上面运行结果输出。

查看源代码once.go,里面有这样的解释:

if once.Do(f) is called multiple times, only the first call will invoke f,

even if f has a different value in each invocation. A new instance of

Once is required for each function to execute.

大概意思是:如果once.Do(f)被调用多次,只有第一次调用才会执行f函数,即使f是不同的函数。为了每一个函数都被执行,就需要不同的Once实例。

我们查看Once类型的定义:

代码语言:javascript
复制
type Once struct {
  m    Mutex
  done uint32
}

源码中其实用到了互斥锁m和标志位done。然后再看Do方法的实现:

代码语言:javascript
复制
func (o *Once) Do(f func()) {
  if atomic.LoadUint32(&o.done) == 1 {
    return
  }
  // Slow-path.
  o.m.Lock()
  defer o.m.Unlock()
  if o.done == 0 {
    defer atomic.StoreUint32(&o.done, 1)
    f()
  }
}

每次调用Do方法之前,用atomic包的LoadUint32函数获取标志位done的值,等于1则说明Do方法已经被调用过,直接return,什么都不做。否则利用互斥锁,保证协程安全的去调用f函数,之后把标志位done置为1。

下面我们看一个例子,来实现单实例:

代码语言:javascript
复制
package main

import (
  "fmt"
  "sync"
  "time"
)

var once sync.Once

var mmp map[int]string

func main() {

  for i := 0; i < 10; i++ {
    go func(i int) {
      once.Do(func (){
         mmp = make(map[int]string, 10)
      })
      fmt.Printf("-----%d------%p\n", i, &mmp)
    }(i)
  }
  //主协程等1min,等上面的10个协程执行完
  time.Sleep(1 * time.Second)
}

我们起10个协程去竞争初始化类型为字典类型的mmp,然后打印每次mmp的地址,运行输出如下:

代码语言:javascript
复制
-----1------0x50cca0
-----3------0x50cca0
-----2------0x50cca0
-----4------0x50cca0
-----7------0x50cca0
-----6------0x50cca0
-----8------0x50cca0
-----9------0x50cca0
-----5------0x50cca0
-----0------0x50cca0

Process finished with exit code 0

我们可以看到mmp每次地址都一样。如此就轻松优雅就实现了和java单例模式相似的效果。

推荐文章:

java单例模式

END

本文由“壹伴编辑器”提供技术支持

我是小碗汤,我们一起学习。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-05-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 进击云原生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档