078-只被执行一次的函数

Posted --Allen--

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了078-只被执行一次的函数相关的知识,希望对你有一定的参考价值。

倘若你看过我的所有文章的话,你肯定还记得曾经在学习《Linux 环境编程》的时候,也有一篇类似的文章,标题和这个一模一样——《只被执行一次的函数》

我强烈建议你回顾一下上面那篇文章。如果你没有《Linux 环境编程》的基础,强烈建议你补习一下,只会 Golang 是不行的。

1. 背景

相信很多同学都使用过单例模式。如果在单线程程序中,单例模式肯定没啥问题,但是如果在多线程程序中,可能就容易出问题了。当然,很多同学想到的方法是加锁来解决,像下面这样:

T* getInstance() 
    mutex.lock()
    if (_instance == nullptr) 
        _instance = new T();
    
    mutex.unlock()

    return _instance;

这个加锁粒度太大了,而且每次访问都需要加锁。也有同学会说,使用 static 全局初始化不就好了(有人叫它为饿汉模式)。没错,这是一种可行的方法,但是缺点是有时候可能一次也用不到这个单例,一上来就初始化有点太浪费。

那使用 double-check 方法改一下(所谓的线各安全的懒汉模式)?

T* getInstance() 
    if (_instance == nullptr) 
        mutex.lock()
        if (_instance == nullptr) 
            _instance = new T();
        
        mutex.unlock()
    
    return _instance;

很不幸运的是,即使是 double-check,上面的代码在多线程环境中还是会出问题。比如线程 1 执行 _instance = new T(),_instance 已经不为 nullptr 了,但是实际上 T 对象还没有构造出来,即构造函数还未执行。(对象构造必须是先申请内存,再执行构造函数)。这个时候线程 2 在第一个 if 条件看到的 _instance 不为 nullptr,于是返回……

看起来好像挺麻烦的。不用怕,我们有杀器,可以更加简单且完美的解决这个问题——那就是 sync.Once 类型。

2. 简单的 demo

为了让你快速体验 sync.Once 的作用,来看下面一段代码。

package main

import (
    "fmt"
    "sync"
)

func hello() 
    fmt.Println("only once")


func main() 
    var once sync.Once
    once.Do(hello) // 把 hello 函数托管给 once 对象去执行。
    fmt.Println("first")
    once.Do(hello) // 第二次托管,once 就不理我们了。。。
    fmt.Println("second")


// Output:
// only once
// first
// second

其实,once.Do 也是“协程”安全的,你甚至可以这样:

func main() 
    var once sync.Once
    var wg sync.WaitGroup
    wg.Add(10)
    // 10 个协程同时托管 hello 函数给 once
    for i := 0; i < 10; i++ 
        go func() 
            once.Do(hello)
            wg.Done()
        ()
    
    wg.Wait()
    fmt.Println("first")
    fmt.Println("second")


// Output:
// only once
// first
// second

3. 使用 once 来实现懒汉单例

package main

import (
    "fmt"
    "sync"
)

type Configure struct
    volume int32


var once sync.Once
var configure *Configure

func initConfig() 
    configure = &Configure10


func GetInstance() *Configure 
    // initConfig()
    once.Do(initConfig) // 注释这一行,打开上一行试试
    return configure


func main() 
    var conf *Configure
    fmt.Printf("%p\\n", conf)

    var wg sync.WaitGroup

    wg.Add(10)
    for i := 0; i < 10; i++ 
        go func() 
            conf = GetInstance()
            fmt.Printf("%p\\n", conf)
            wg.Done()
        ()
    
    wg.Wait()

4. 总结

  • 掌握 sync.Once 的作用和使用方法

以上是关于078-只被执行一次的函数的主要内容,如果未能解决你的问题,请参考以下文章

UVA-10859 Placing Lampposts

C++不是说虚基类构造函数只被调用一次吗?

仅在元素完全加载时执行一次的函数

只执行一次的js 函数。

js中setInterval()函数只执行一次的原因

为啥析构函数被调用两次而构造函数只被调用一次?