sync.Once-保证运行期间的某段代码只会执行一次

news2024/12/23 14:59:30

初入门径


sync.Once提供了保证某个操作只被执行一次的功能,其最常应用于单例模式之下,例如初始化系统配置、保持数据库唯一连接,以及并发访问只需要初始化一次的共享资源。

单例模式有懒汉模式饿汉模式两种

饿汉模式 顾名思义就是比较饥饿,所以一上来(服务启动时)就初始化。
懒汉模式 顾名思义就是偷懒,在获取实例的时候在进行初始化,但懒汉模式会有并发问题:有可能多个 goruntine 同时获取 对象都是 nil ,然后都开始创建了实例,就不满足单例模式了。解决办法是加锁

sync.Once就是懒汉模式

Go并发编程 — sync.Once[1]

package main

import (
 "fmt"
 "sync"
)

func main() {
 var once sync.Once
 fun1 := func() {
  fmt.Println("一只老虎")
 }
 once.Do(fun1)

 fun2 := func() {
  fmt.Println("两只老虎")
 }

 once.Do(fun2)
}

输出为:

一只老虎

并发调用 Do()

package main

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

func main() {
 var once sync.Once
 for i := 0; i < 5; i++ {
  go func(i int) {
   fun1 := func() {
    fmt.Printf("i:=%d\n", i)
   }
   once.Do(fun1)
  }(i)
 }

 // 为防止主goroutine直接运行完,什么都看不到
 time.Sleep(50 * time.Millisecond)
}

在上面这段代码里,开启了5个并发的 goroutine ,不管执行多少次, 始终只打印一次. 至于 i 是多少,就看先执行的是哪个 g 了。

Once 保证只有第一次调用 Do() 方法时, 传递的 f (无参数无返回值的函数) 才会执行,并且之后不管调用的参数是否改变了,也不管f执行时是否发生了panic,之后都不再执行。


这段是官方Demo:

 package main

 import (
     "fmt"
    "sync"
 )
 
 func main() {
     var once sync.Once
    onceBody := func() {
        fmt.Println("Only once")
    }
    done := make(chan bool)
    for i := 0; i < 10; i++ {
        go func() {
            once.Do(onceBody)
            done <- true
        }()
    }
    for i := 0; i < 10; i++ {
        <-done
    }
}
// 结果只打印一次:only once



源码实现


其代码实现很简洁, 从头到尾加注释不超过 70 行. 对外暴露了一个唯一接口 Do(f func()), 使用起来也很简单,可以多次调用,但是只有第一次调用Do方法时f参数才会执行,这里的f是一个无参数无返回值的函数。


点击查看 sync.Once源码:
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package sync

import (
 "sync/atomic"
)

// Once is an object that will perform exactly one action.
type Once struct {
 // done indicates whether the action has been performed.
 // It is first in the struct because it is used in the hot path.
 // The hot path is inlined at every call site.
 // Placing done first allows more compact instructions on some architectures (amd64/x86),
 // and fewer instructions (to calculate offset) on other architectures.

    // done用来表示当前的操作是否已经被执行, 0表示还没有执行过, 1表示已经执行
    // done属性放在结构体的第一位,是因为它在hot path中使用
    // hot path在每个调用点会被内联
    // 将done放在结构体首位,像amd64/386等架构上可以允许更多的压缩指令,且在其他架构上更少的指令去计算偏移量
 done uint32
 m    Mutex
}

// Do calls the function f if and only if Do is being called for the
// first time for this instance of Once. In other words, given
//  var once Once
// 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.
//
// Do is intended for initialization that must be run exactly once. Since f
// is niladic, it may be necessary to use a function literal to capture the
// arguments to a function to be invoked by Do:
//  config.once.Do(func() { config.init(filename) })
//
// Because no call to Do returns until the one call to f returns, if f causes
// Do to be called, it will deadlock.
//
// If f panics, Do considers it to have returned; future calls of Do return
// without calling f.
//

// Do的作用主要是针对初始化且有且只能执行一次的场景。因为Do直到f返回才返回,`
// 所以如果f内再调用Do则会导致死锁
// 如果f执行过程中panic了 那么Do任务f已经执行完毕 未来再次调用不会再执行f

func (o *Once) Do(f func()) {
 // Note: Here is an incorrect implementation of Do:
 //
 // if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
 //  f()
 // }
 //
 // Do guarantees that when it returns, f has finished.
 // This implementation would not implement that guarantee:
 // given two simultaneous calls, the winner of the cas would
 // call f, and the second would return immediately, without
 // waiting for the first's call to f to complete.
 // This is why the slow path falls back to a mutex, and why
 // the atomic.StoreUint32 must be delayed until after f returns.

 if atomic.LoadUint32(&o.done) == 0 {
  // Outlined slow-path to allow inlining of the fast-path.
        // 原子获取 done 的值,判断 done 值是否为 0,如果为 0 就调用 doSlow 方法,进行二次检查。
        // 可能会存在并发 进入slow-path
  o.doSlow(f)
 }
}

func (o *Once) doSlow(f func()) {
    // 二次检查时,持有互斥锁,保证只有一个 goroutine 执行。
 o.m.Lock()
 defer o.m.Unlock()
 if o.done == 0 { //二次判断f是否已经被执行
         // 二次检查,如果 done 的值仍为 0,则认为是第一次执行,执行参数 f,并将 done 的值设置为 1。

         //即使此时有多个 goroutine 同时进入了 doSlow 方法,因为双检查的机制,后续的 goroutine 会看到 o.done 的值为 1,也不会再次执行 f
  defer atomic.StoreUint32(&o.done, 1)
  f()
 }
}

Once 结构体中包含两个字段, 分别是 uint32 类型的 done 和 Mutex 类型的 m.

Once 实现了两个方法, 分别是 DodoSlow。其中 doSlow 是一个非可导出方法,只能在内部被 Do 方法调用.

Done 方法先原子获取 done 的值,如果 done 的值为 0,则调用 doSlow 方法进行二次检查,二次检查时,持有互斥锁,保证只有一个 goroutine 执行操作.如果二次检查的结果为 0,则认为是第一次执行,程序执行函数类型的参数 f,然后将 done 的值设置为 1.




注意事项


package main

import (
 "fmt"
 "sync"
)

func main() {

 panicDo()

 //nestedDo()
 nestedDo2()

}

func panicDo() {
 once := &sync.Once{}
 defer func() {
  if err := recover(); err != nil {
   once.Do(func() {
    fmt.Println("run in recover")
   })
  }
 }()
 once.Do(func() {
  panic("panic i=0")
 })
}

func nestedDo() {
 once := &sync.Once{}
 once.Do(func() {
  once.Do(func() {
   fmt.Println("test nestedDo")
  })
 })
}

func nestedDo2() {
 once1 := &sync.Once{}
 once2 := &sync.Once{}
 once1.Do(func() {
  once2.Do(func() {
   fmt.Println("test nestedDo")
  })
 })
}

详解并发编程之sync.Once的实现(附上三道面试题)[2]

(1). sync.Once()方法中传入的函数发生了panic,重复传入还会执行吗?

执行panicDo方法,不会打印任何东西. sync.Once.Do 方法中传入的函数只会被执行一次,哪怕函数中发生了 panic;


(2). sync.Once()方法传入的函数中再次调用sync.Once()方法会有什么问题吗?

会发生死锁! 执行nestedDo方法,会报 fatal error: all goroutines are asleep - deadlock! 根据源码实现,可知在第二个do方法会一直等doshow()中锁的释放导致发生了死锁;


(3). 执行nestedDo2,会输出什么?

会打印出 test nestedDo. once1,once2是两个对象,互不影响. 所以sync.Once是使方法只执行一次对象的实现。




官方库或知名项目中的使用


net系统的网络配置 就是存放在一个变量里,用sync.Once控制读写

alt

当且仅当第一次读某个变量时,进行初始化(写操作)

变量被初始化过程中,所有读都被阻塞(读操作;当变量初始化完成后,读操作继续进行

变量仅初始化一次,初始化完成后驻留在内存里

Golang package sync 剖析(一): sync.Once[3]




参考:

你真的了解 sync.Once 吗[4]

深入源码分析golang之sync.Once[5]

参考资料

[1]

Go并发编程 — sync.Once: https://juejin.cn/post/7093394883875962894

[2]

详解并发编程之sync.Once的实现(附上三道面试题): https://segmentfault.com/a/1190000040038329

[3]

Golang package sync 剖析(一): sync.Once: https://segmentfault.com/a/1190000040044135

[4]

你真的了解 sync.Once 吗: https://segmentfault.com/a/1190000040042235

[5]

深入源码分析golang之sync.Once: https://segmentfault.com/a/1190000040043353

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1015800.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【看好了】如何使用fiddler实现手机抓包,Filters过滤器!

一、Fiddler与其他抓包工具的区别  1、Firebug虽然可以抓包&#xff0c;但是对于分析http请求的详细信息&#xff0c;不够强大。模拟http请求的功能也不够&#xff0c;且firebug常常是需要“无刷新修改”&#xff0c;如果刷新了页面&#xff0c;所有的修改都不会保存&#xff…

CDH集群部署

文章目录 1. 资源准备2. 部署 Mariadb 数据库3. 安装CM服务4. 安装数据节点5. 登录CM系统 1. 资源准备 准备好CDH安装包资源&#xff0c;官方网站下载需要账号&#xff0c;如果没有账号可以去网上到处搜搜。主要涉及到的资源有&#xff1a; cloudera-manager-servercloudera-m…

力扣:100. 相同的树(Python3)

题目&#xff1a; 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣…

Mars3d插件参考开发教程并在相关页面引入

问题场景&#xff1a; 1.在使用Mars3d热力图功能时&#xff0c;提示mars3d.layer.HeatLayer is not a constructor 问题原因: 1.mars3d的热力图插件mars3d-heatmap没有安装引用。 解决方案&#xff1a; 1.参考开发教程&#xff0c;找到相关的插件库&#xff1a;Mars3D 三维…

【LeetCode-简答题】242. 有效的字母异位词

文章目录 题目方法一&#xff1a;数组存放&#xff1a;方法二&#xff1a;哈希存放 题目 方法一&#xff1a;数组存放&#xff1a; class Solution {public boolean isAnagram(String s, String t) {int[] s1 new int[26];int[] t1 new int[26];for(int i 0; i< s.lengt…

【LeetCode-简单题】350. 两个数组的交集 II

文章目录 题目方法一&#xff1a;哈希表方法二&#xff1a;双指针 题目 方法一&#xff1a;哈希表 用哈希表记录第一个数组的每个数和每个数的出现次数再遍历第二个数组&#xff0c;如果哈希表中有这个数&#xff0c;并且次数还不为0&#xff0c;说明是交集元素&#xff0c;加…

RocketMQ实践与原理分析(Docker安装RocketMQ)

前言 QBM之前使用的消息中间件是ActiveMQ&#xff0c;后续需要升级为RocketMQ。 MQ广泛应用于很多业务场景中&#xff0c;主要的作用 异步解耦削峰… 常用MQ中间件对比&#xff0c;参考官方文档&#xff1a;https://rocketmq.apache.org/zh/docs/4.x/introduction/03whatis…

Android查看公钥与MD5

参考&#xff1a;填写App特征信息_备案-阿里云帮助中心 安卓应用获取App特征信息指导 包名、公钥和签名MD5获取方式有多种&#xff0c;本文以使用JadxGUI工具获取为例。 下载JadxGUI工具&#xff1a;GitHub - skylot/jadx: Dex to Java decompiler下载安装完成后&#xff0c;使…

饲料添加剂 微生物 屎肠球菌

声明 本文是学习GB 7300.503-2023 饲料添加剂 第5部分&#xff1a;微生物 屎肠球菌. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件规定了饲料添加剂屎肠球菌的技术要求、采样、检验规则、标签、包装、运输、贮存和保质 期&#xff0…

Routing路径系列数学建模(TSP+CVRP)

1.Traveling Salesperson Problem(TSP) 参考&#xff1a;维基百科TSP 给定一些城市和城市之间的距离&#xff0c;找到最短路径&#xff0c;经过每个城市最后返回起点&#xff0c;组合优化问题中属于NP-hard难度。对于TSP问题有两类混合整数规划模型&#xff1a;Miller–Tucker…

PYTHON-模拟练习题目集合

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

QScrollBar滚动条、QSlider滑块、 QDial表盘

QAbstractSlider 类、 QSCrollBar 类、 QSlider 类 一、 基本原理 1、 QAbstractSlider 继承自 QWidget&#xff0c;该类主要用于提供一个范围内的整数值&#xff0c; 2、 QAbstractSlider 类是 QScrollBar 类(滚动条)、 QSlider 类(滑块)、 QDial 类(表盘)的父类&#xff0c;因…

智能公厕大脑,提高城市公共厕所管理效率!

随着城市建设的不断发展&#xff0c;公共设施的完善也成为人们关注的重要问题之一。而城市公共厕所作为城市治理的一部分&#xff0c;管理效率和运营成本则直接关系到城市环境整体卫生和市民的生活质量。如何提高城市公共厕所管理能力和服务水平&#xff0c;成为城市治理和市民…

MQ的初步了解

目录 什么是MQ&#xff1f; 为什么要用MQ&#xff08;MQ的优点&#xff09;&#xff1f; MQ的缺点 常用的MQ产品 MQ使用中的常见问题 什么是MQ&#xff1f; 【1】MQ&#xff1a;MessageQueue&#xff0c;消息队列。 队列&#xff0c;是一种FIFO 先进先出的数据结构。消息由…

28335 GPIO作为输入的配置记录

28335 GPIO配置为输入&#xff0c;可以启动输入滤波功能&#xff0c;看了网上很多的讲解&#xff0c;把滤波配置记录一下&#xff1a; 主要是配置两个参数&#xff1a; GpioCtrlRegs.GPXCTRL.bit.QUALPRDX &#xff1a;用于配置采样的周期&#xff0c;由配置值和SYSCLKOUT共同…

Java面试题之——异常和错误

提示&#xff1a;解释Java中的异常和错误是什么&#xff0c;以及它们之间的区别是什么&#xff1f; 文章目录 前言从定义上来说&#xff1a;从处理方式来看&#xff1a;总结⭐️ 好书推荐 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 在Java编程语言…

PostgreSQL 如果想知道表中某个条件查询条件在索引中效率 ?

开头还是介绍一下群&#xff0c;如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,SQL Server,Redis &#xff0c;Oracle ,Oceanbase 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请加微信号 liuaustin3 &…

NSS [HNCTF 2022 Week1]Challenge__rce

NSS [HNCTF 2022 Week1]Challenge__rce hint:灵感来源于ctfshow吃瓜杯Y4大佬的题 开题&#xff0c;界面没东西&#xff0c;源码里面有注释&#xff0c;GET传参?hint 传参后返回了源码 <?php error_reporting(0); if (isset($_GET[hint])) {highlight_file(__FILE__); }…

如何判断linux 文件(或lib)是由uclibc还是glibc编译出来的?

工作中使用的编译环境有2套编译器&#xff0c;一个是glibc&#xff0c;一个是uclibc。 有些项目使用的glibc编译的lib&#xff0c;和使用uclibc编译的工程&#xff0c;在一起就会出现reference的编译错误如下&#xff1a; 那和如何来判断一个文件是由哪个编译器编译的呢&#…

苹果cms大橙子vfed 5.0去授权完美破解主题模板

大橙模版算是在苹果 cms 众多主题里&#xff0c;较为亮眼的一款了&#xff0c;主题简洁&#xff0c;功能众多&#xff0c;非常的齐全。 今天分享的就是大橙 5.0 版本模板&#xff0c;自定义菜单输入下列代码使用主题设置和资源采集。 vfed 主题设置&#xff0c;/index.php/la…