zoukankan      html  css  js  c++  java
  • 利用golang优雅的实现单实例

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

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

    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")
    }
    

    运行程序输出如下结果:

    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类型的定义:

    type Once struct {
     m    Mutex
     done uint32
    }
    

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

    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。

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

    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
    ", i, &mmp)
       }(i)
     }
     //主协程等1min,等上面的10个协程执行完
     time.Sleep(1 * time.Second)
    }
    

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

    -----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单例模式



    本公众号免费提供csdn下载服务,海量IT学习资源,如果你准备入IT坑,励志成为优秀的程序猿,那么这些资源很适合你,包括但不限于java、go、python、springcloud、elk、嵌入式 、大数据、面试资料、前端 等资源。同时我们组建了一个技术交流群,里面有很多大佬,会不定时分享技术文章,如果你想来一起学习提高,可以公众号后台回复【2】,免费邀请加技术交流群互相学习提高,会不定期分享编程IT相关资源。


    扫码关注,精彩内容第一时间推给你

    image

  • 相关阅读:
    Matrix Walk CodeForces
    String Typing CodeForces
    Zebras CodeForces
    【OCP-12c】2019年CUUG OCP 071考试题库(74题)
    【OCP-12c】2019年CUUG OCP 071考试题库(73题)
    【OCP题库-12c】最新CUUG OCP 071考试题库(72题)
    【OCP题库-12c】最新CUUG OCP 071考试题库(71题)
    【OCP题库-12c】最新CUUG OCP 071考试题库(70题)
    【OCP题库-12c】最新CUUG OCP 071考试题库(69题)
    【OCP题库】最新CUUG OCP 12c 071考试题库(68题)
  • 原文地址:https://www.cnblogs.com/liabio/p/11696064.html
Copyright © 2011-2022 走看看