• 转: Go -- 单例


    最近在学习Golang,想着可以就以前的知识做一些串通,加上了解到go语言也是面向对象编程语言之后。在最近的开发过程中,我碰到一个问题,要用go语言实现单例模式。本着“天下知识,同根同源”(我瞎掰的~),我心想,这有什么难的,可是真正做起来,还是碰到了不少问题。

      下面是我的经历:

      1.我先是完成了我的第一版单例模式,就是非并发,最简单的一种,懒汉模式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var instance *single
    type single struct{
        Name string
    }
    func GetInstance()*single{
        if m == nil{
            m = &single{}
        }
        return m
    }
    func main(){
        a := GetInstance()
        a.Name = "a"
        b := GetInstance()
        b.Name = "b"
        fmt.Println(&a.Name, a)
        fmt.Println(&b.Name, b)
        fmt.Printf("%p %T ", a, a)
        fmt.Printf("%p %T ", b, b)
    }

      结果如下:

    0xc04203e1b0 &{b}
    0xc04203e1b0 &{b}
    0xc04203e1b0 *main.single
    0xc04203e1b0 *main.single

      可以看到,我们已经实现了简单的单例模式,我们申请了两次实例,在改变一个第二个实例的字段之后,第一个也随之改变了。而且从他们的地址都相同也可以看出是同一个对象。但是,这样简陋的单例模式在并发下就容易出错,非线程安全的。

      现在我们是在并发的情况下去调用的 GetInstance函数,现在恰好第一个goroutine执行到m = &Manager {}这句话之前,第二个goroutine也来获取实例了,第二个goroutine去判断m是不是nil,因为m = &Manager{}还没有来得及执行,所以m肯定是nil,现在出现的问题就是if中的语句可能会执行两遍!

      2.紧接着我们做了一些改进,给单例模式加了锁:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var m *single
    var lock sync.Mutex
    type single struct{
        Name string
    }
    func GetInstance()*single{
        lock.Lock()
        defer lock.Unlock()
        if m == nil{
            m = &single{}
        }
        return m
    }

      结果同上。

      与此同时,新的问题出现了,在高并发环境下,现在不管什么情况下都会上一把锁,而且加锁的代价是很大的,有没有办法继续对我们的代码进行进一步的优化呢?

      3.双重锁机制:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var m *single
    var lock sync.Mutex
    type single struct{
        Name string
    }
    func GetInstance()*single{
        if m == nil{
            lock.Lock()
            defer lock.Unlock()
            if m == nil{
                m = &single{}
            }
        }
        return m
    }

      这次我们用了两个判断,而且我们将同步锁放在了条件判断之后,这样做就避免了每次调用都加锁,提高了代码的执行效率。理论上写到这里已经是很完美的单例模式了,但是我们在go语言里,我们有一个很优雅的写法。

      4.sync包里的Once.Do()方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var m *single
    var once sync.Once
     
    type single struct{
        Name string
    }
    func GetInstance()*single{
        once.Do(func() {
            m = &single{}
        })
        return m
    }

      Once.Do方法的参数是一个函数,这里我们给的是一个匿名函数,在这个函数中我们做的工作很简单,就是去赋值m变量,而且go能保证这个函数中的代码仅仅执行一次!

  • 相关阅读:
    tensorflow入门(三)
    tensorflow入门(二)
    setTimeout
    PreResultListener
    sql 删除重复记录
    oracle dual表用途及结构详解
    oracle中的dual表
    Dubbo远程调用服务框架原理与示例
    struts2和spring的两种整合方式
    Timer和TimerTask详解
  • 原文地址:https://www.cnblogs.com/mafeng/p/10035335.html
Copyright © 2020-2023  润新知