逐步学习Go-sync.Once(只执行一次)Exactly Once

2024-04-05 20:36

本文主要是介绍逐步学习Go-sync.Once(只执行一次)Exactly Once,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

sync.Once简介

sync.Once 是一个会执行且仅执行一次动作的对象。该对象在第一次使用后不能再被复制。

在 Go 内存模型的术语中,sync.Once 的 Do 方法中的函数 f 返回的操作,相对于任何对 once.Do(f) 的调用返回的操作,都具有“同步优先”的顺序。简单来说,即使在并发环境下,函数 f 也只会在首次调用 once.Do(f) 时执行。

每个 sync.Once 对象仅适用于执行一次动作。也就是说,如果多次调用了 once.Do(f),仅第一次的调用会激发函数 f 的执行,即使每次调用 once.Do(f) 时函数 f 的值都有所不同。

sync.Once 一般用于必须仅初始化一次的场景。由于作为 Do 方法参数的函数没有传入的参数,如果你需要在由 Do 方法调用的函数中使用特定的参数,你可能需要使用闭包:

config.once.Do(func() { config.init(filename) })

COPY

由于 Do 方法在其内部的函数返回之前不会返回,如果函数 f 导致 Do 方法被调用,就会引发死锁。

如果 Do 方法的函数 f 发生 panic,sync.Once 会认为函数 f 已执行完毕。以后再调用 Do 方法时,不会再执行函数 f

功能特性测试

sync.Once还是比较简单的,而且源代码也特别简单,我们来列举几个场景测试。
以下是每个测试用例所覆盖的特性和场景的描述:

  1. TestOnce_ShouldExecuteOnce_WhenExecuteOnlyOnce:这个测试用例验证了sync.Once的基本特性 – 即确保一段代码在非并发环境中只执行一次。

  2. TestOnce_ShouldExecuteOnce_WhenExecutedOnceAndExecuteAgain:这个测试用例验证了即使sync.OnceDo方法被多次调用,内部的函数也只执行一次。这是sync.Once的核心特性,用于确保一个操作在整个程序运行期间只执行一次,无论这个操作被尝试执行多少次。

  3. TestOnce_ShouldNotExecute_WhenFunctionPanicked:这个测试用例验证了当sync.OnceDo方法的函数执行过程中发生panic时,随后的Do调用将不会继续执行函数。这是sync.Once的一个重要特性,它确保了即使在面临错误处理的情况下,被追踪的函数只执行一次。

  4. TestOnce_ShouldExecuteAgain_WhenPreviousExecutionPaniced:这个测试用例验证了即使Do方法的函数因为panic而没有正常执行,sync.Once也会认为该函数已经执行过,并且不会在后续的Do方法调用中再次尝试执行函数。这是sync.Once的另一个重要特性,它可以帮助规避错误以及异常的发生。

  5. TestOnce_ShouldExecuteOnce_WhenCalledInMultipleGoroutines:这个测试用例验证了即使在多个goroutine并发调用sync.OnceDo方法时,函数也只会执行一次。这是sync.Once被设计用来解决的主要问题,也是sync.Once在并发编程中的一个重要应用场景。

测试代码


import ("sync""sync/atomic""testing""github.com/stretchr/testify/assert"
)func TestOnce_ShouldExecuteOnce_WhenExecuteOnlyOnce(t *testing.T) {var i intonce := sync.Once{}once.Do(func() {i = 1000})assert.Equal(t, 1000, i)
}func TestOnce_ShouldExecuteOnce_WhenExecutedOnceAndExecuteAgain(t *testing.T) {var i intonce := sync.Once{}once.Do(func() {i = 1000})once.Do(func() {i = 2000})assert.Equal(t, 1000, i)
}func TestOnce_ShouldNotExecute_WhenFunctionPanicked(t *testing.T) {var num intonce := sync.Once{}// 第一次调用应该 panicassert.Panics(t, func() {once.Do(func() {panic("Error")})})// 因为第一次panic了,此时再调用 Do 方法,函数 f 不应该被执行once.Do(func() {num = 1000})// 因为 num 的值没有被改变,所以应该还是 0assert.Equal(t, num, 0)
}func TestOnce_ShouldExecuteAgain_WhenPreviousExecutionPaniced(t *testing.T) {var num int = 0once := sync.Once{}// 第一次调用应该 panicassert.Panics(t, func() {once.Do(func() {panic("Error")})})// 第一次 panic 后,下一次调用应该正确执行once.Do(func() {num = 1000})assert.Equal(t, 0, num)
}func TestOnce_ShouldExecuteOnce_WhenCalledInMultipleGoroutines(t *testing.T) {var num atomic.Int32once := sync.Once{}wg := sync.WaitGroup{}wg.Add(100)// 启动 100 个 goroutine, 都尝试执行once.Dofor i := 0; i < 100; i++ {go func() {once.Do(func() {num.Add(1)})wg.Done()}()}wg.Wait()assert.Equal(t, int32(1), num.Load())
}

COPY

sync.Once源码


type Once struct {// 用来标识操作是否已经执行过done atomic.Uint32 // 用来在多个并发的`Do`调用中,保证只有一个可以执行函数`f`m    Mutex         
}func (o *Once) Do(f func()) {if o.done.Load() == 0 {// 如果操作还没执行过,进入doSlow方法o.doSlow(f)}
}func (o *Once) doSlow(f func()) {o.m.Lock()defer o.m.Unlock()if o.done.Load() == 0 {// 确保无论f是否panic,done都会被设置为1defer o.done.Store(1) // 执行用户传入的函数ff()                   }
}

COPY

sync.Once能用来干啥?

  1. 单例模式
  2. 延迟初始化
  3. 并发控制

原文链接

逐步学习Go-sync.Once(只执行一次)Exactly Once – FOF编程网

这篇关于逐步学习Go-sync.Once(只执行一次)Exactly Once的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/878084

相关文章

Go语言使用select监听多个channel的示例详解

《Go语言使用select监听多个channel的示例详解》本文将聚焦Go并发中的一个强力工具,select,这篇文章将通过实际案例学习如何优雅地监听多个Channel,实现多任务处理、超时控制和非阻... 目录一、前言:为什么要使用select二、实战目标三、案例代码:监听两个任务结果和超时四、运行示例五

解密SQL查询语句执行的过程

《解密SQL查询语句执行的过程》文章讲解了SQL语句的执行流程,涵盖解析、优化、执行三个核心阶段,并介绍执行计划查看方法EXPLAIN,同时提出性能优化技巧如合理使用索引、避免SELECT*、JOIN... 目录1. SQL语句的基本结构2. SQL语句的执行过程3. SQL语句的执行计划4. 常见的性能优

go动态限制并发数量的实现示例

《go动态限制并发数量的实现示例》本文主要介绍了Go并发控制方法,通过带缓冲通道和第三方库实现并发数量限制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录带有缓冲大小的通道使用第三方库其他控制并发的方法因为go从语言层面支持并发,所以面试百分百会问到

Go语言并发之通知退出机制的实现

《Go语言并发之通知退出机制的实现》本文主要介绍了Go语言并发之通知退出机制的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、通知退出机制1.1 进程/main函数退出1.2 通过channel退出1.3 通过cont

Spring Bean初始化及@PostConstruc执行顺序示例详解

《SpringBean初始化及@PostConstruc执行顺序示例详解》本文给大家介绍SpringBean初始化及@PostConstruc执行顺序,本文通过实例代码给大家介绍的非常详细,对大家的... 目录1. Bean初始化执行顺序2. 成员变量初始化顺序2.1 普通Java类(非Spring环境)(

Spring Boot 中的默认异常处理机制及执行流程

《SpringBoot中的默认异常处理机制及执行流程》SpringBoot内置BasicErrorController,自动处理异常并生成HTML/JSON响应,支持自定义错误路径、配置及扩展,如... 目录Spring Boot 异常处理机制详解默认错误页面功能自动异常转换机制错误属性配置选项默认错误处理

如何在Java Spring实现异步执行(详细篇)

《如何在JavaSpring实现异步执行(详细篇)》Spring框架通过@Async、Executor等实现异步执行,提升系统性能与响应速度,支持自定义线程池管理并发,本文给大家介绍如何在Sprin... 目录前言1. 使用 @Async 实现异步执行1.1 启用异步执行支持1.2 创建异步方法1.3 调用

Go语言编译环境设置教程

《Go语言编译环境设置教程》Go语言支持高并发(goroutine)、自动垃圾回收,编译为跨平台二进制文件,云原生兼容且社区活跃,开发便捷,内置测试与vet工具辅助检测错误,依赖模块化管理,提升开发效... 目录Go语言优势下载 Go  配置编译环境配置 GOPROXYIDE 设置(VS Code)一些基本

使用Go实现文件复制的完整流程

《使用Go实现文件复制的完整流程》本案例将实现一个实用的文件操作工具:将一个文件的内容完整复制到另一个文件中,这是文件处理中的常见任务,比如配置文件备份、日志迁移、用户上传文件转存等,文中通过代码示例... 目录案例说明涉及China编程知识点示例代码代码解析示例运行练习扩展小结案例说明我们将通过标准库 os

Spring Boot Maven 插件如何构建可执行 JAR 的核心配置

《SpringBootMaven插件如何构建可执行JAR的核心配置》SpringBoot核心Maven插件,用于生成可执行JAR/WAR,内置服务器简化部署,支持热部署、多环境配置及依赖管理... 目录前言一、插件的核心功能与目标1.1 插件的定位1.2 插件的 Goals(目标)1.3 插件定位1.4 核