Skip to content

Latest commit

 

History

History

lesson22

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

sync.Once

定义

Once是sync包里的一个结构体类型,Once可以在并发场景下让某个操作只执行一次,比如设计模式里的单例只创建一个实例,比如只加载一次配置文件,比如对同一个channel只关闭一次(对一个已经close的channel再次close会引发panic)等。

定义如下:

type Once struct {
    // some fields
}

这个结构体只有1个方法Do,参数是要执行的函数。(注意:参数是函数类型,而不是函数的返回值,所以只需要把函数名作为参数给到Do即可)

可以看到Do方法的参数f这个函数类型没有参数,所以如果要执行的函数f需要传递参数就要结合Go的闭包来使用。

func(o *Once) Do(f func())

参考下面的例子,print函数通过Once执行,只会执行1次

package main

import (
    "fmt"
    "sync"
)

func print() {
    fmt.Println("test once")
}

func main() {
    var wg sync.WaitGroup
    var once sync.Once
    size := 10
    wg.Add(size)
    
    /*启用size个goroutine,每个goroutine都调用once.Do(print)
    最终print只会执行一次
    */
    for i:=0; i<size; i++ {
        go func() {
            defer wg.Done()
            once.Do(print)
        }()
    }
    /*等待所有goroutine执行完成*/
    wg.Wait()
    fmt.Println("end")
}

sync.Once实现并发安全的单例

package main

import (
    "fmt"
    "sync"
)

type Singleton struct {
    member int
}

var instance *Singleton

var once sync.Once

func getInstance() *Singleton {
    /*
    通过sync.Once实现单例,只会生成一个Singleton实例
    */
    once.Do(func() {
        fmt.Println("once")
        instance = &Singleton{}
        instance.member = 100
    })
    fmt.Println(instance.member)
    return instance
}

func main() {
    var wg sync.WaitGroup
    size := 10
    wg.Add(size)
    /*
    多个goroutine同时去获取Singelton实例
    */
    for i:=0; i<size; i++ {
        go func() {
            defer wg.Done()
            instance = getInstance()
        }()
    }
    wg.Wait()
    fmt.Println("end")
}

注意事项

  • Once变量作为函数参数传递时,只能传指针,不能传值。传值给函数A的话,对于函数A而言,参数列表里的once形参会是一个新生成的once局部变量,和外部传入的once实参不一样。

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func test() {
        fmt.Println("test once")
    }
    
    func print(once *sync.Once) {
        once.Do(test)
    }
    
    func main() {
        var wg sync.WaitGroup
        var once sync.Once
        size := 10
        wg.Add(size)
        
        /*启用size个goroutine,每个goroutine都调用once.Do(print)
        最终print只会执行一次
        */
        for i:=0; i<size; i++ {
            go func() {
                defer wg.Done()
                print(&once)
            }()
        }
        /*等待所有goroutine执行完成*/
        wg.Wait()
        fmt.Println("end")
    }
  • 如果once.Do(f)方法调用的函数f发生了panic,那Do也会认为函数f已经return了。

  • 如果多个goroutine执行了都去调用once.Do(f),只有某次的函数f调用返回了,所有Do方法调用才会返回,否则Do方法会一直阻塞等待。如果在f里继续调用同一个once变量的Do方法,就会死锁了,因为Do在等待f返回,f又在等待Do返回。