返回顶部
首页 > 资讯 > 精选 >go并发利器sync.Once如何使用
  • 228
分享到

go并发利器sync.Once如何使用

2023-07-05 12:07:57 228人浏览 独家记忆
摘要

这篇文章主要介绍了Go并发利器sync.Once如何使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇go并发利器sync.Once如何使用文章都会有所收获,下面我们一起来看看吧。1. 简介本文主要介绍 Go

这篇文章主要介绍了Go并发利器sync.Once如何使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇go并发利器sync.Once如何使用文章都会有所收获,下面我们一起来看看吧。

1. 简介

本文主要介绍 Go 语言中的 Once 并发原语,包括 Once 的基本使用方法、原理和注意事项,从而对 Once 的使用有基本的了解。

2. 基本使用

2.1 基本定义

sync.Once是Go语言中的一个并发原语,用于保证某个函数只被执行一次。Once类型有一个Do方法,该方法接收一个函数作为参数,并在第一次调用时执行该函数。如果Do方法被多次调用,只有第一次调用会执行传入的函数。

2.2 使用方式

使用sync.Once非常简单,只需要创建一个Once类型的变量,然后在需要保证函数只被执行一次的地方调用其Do方法即可。下面是一个简单的例子:

var once sync.Oncefunc initOperation() {    // 这里执行一些初始化操作,只会被执行一次}func main() {    // 在程序启动时执行initOperation函数,保证初始化只被执行一次    once.Do(initOperation)       // 后续代码}

2.3 使用例子

下面是一个简单使用sync.Once的例子,其中我们使用sync.Once来保证全局变量config只会被初始化一次:

package mainimport (    "fmt"    "sync")var (    config map[string]string    once   sync.Once)func loadConfig() {    // 模拟从配置文件中加载配置信息    fmt.Println("load config...")    config = make(map[string]string)    config["host"] = "127.0.0.1"    config["port"] = "8080"}func GetConfig() map[string]string {    once.Do(loadConfig)    return config}func main() {    // 第一次调用GetConfig会执行loadConfig函数,初始化config变量    fmt.Println(GetConfig())    // 第二次调用GetConfig不会执行loadConfig函数,直接返回已初始化的config变量    fmt.Println(GetConfig())}

在这个例子中,我们定义了一个全局变量config和一个sync.Once类型的变量once。在GetConfig函数中,我们通过调用once.Do方法来保证loadConfig函数只会被执行一次,从而保证config变量只会被初始化一次。 运行上面的程序,输出如下:

load config...map[host:127.0.0.1 port:8080]map[host:127.0.0.1 port:8080]

可以看到,GetConfig函数在第一次调用时执行了loadConfig函数,初始化了config变量。在第二次调用时,loadConfig函数不会被执行,直接返回已经初始化的config变量。

3. 原理

下面是sync.Once的具体实现如下:

type Once struct {   done uint32   m    Mutex}func (o *Once) Do(f func()) {        // 判断done标记位是否为0   if atomic.LoadUint32(&o.done) == 0 {      // Outlined slow-path to allow inlining of the fast-path.      o.doSlow(f)   }}func (o *Once) doSlow(f func()) {   // 加   o.m.Lock()   defer o.m.Unlock()   // 执行双重检查,再次判断函数是否已经执行   if o.done == 0 {      defer atomic.StoreUint32(&o.done, 1)      f()   }}

sync.Once的实现原理比较简单,主要依赖于一个done标志位和一个互斥锁。当Do方法被第一次调用时,会先原子地读取done标志位,如果该标志位为0,说明函数还没有被执行过,此时会加锁并执行传入的函数,并将done标志位置为1,然后释放锁。如果标志位为1,说明函数已经被执行过了,直接返回。

4. 使用注意事项

4.1 不能将sync.Once作为函数局部变量

下面是一个简单的例子,说明将 sync.Once 作为局部变量会导致的问题:

var config map[string]stringfunc initConfig() {    fmt.Println("initConfig called")    config["1"] = "hello world"}func getConfig() map[string]string{    var once sync.Once    once.Do(initCount)    fmt.Println("getConfig called")}func main() {    for i := 0; i < 10; i++ {        go getConfig()    }    time.Sleep(time.Second)}

这里初始化函数会被多次调用,这与initConfig 方法只会执行一次的预期不符。这是因为将 sync.Once 作为局部变量时,每次调用函数都会创建新的 sync.Once 实例,每个 sync.Once 实例都有自己的 done 标志,多个实例之间无法共享状态。导致初始化函数会被多次调用。

如果将 sync.Once 作为全局变量或包级别变量,就可以避免这个问题。所以基于此,不能定义sync.Once 作为函数局部变量来使用。

4.2 不能在once.Do中再次调用once.Do

下面举一个在once.Do方法中再次调用once.Do 方法的例子:

package mainimport ("fmt""sync")func main() {   var once sync.Once   var onceBody func()   onceBody = func() {      fmt.Println("Only once")      once.Do(onceBody) // 再次调用once.Do方法   }   // 执行once.Do方法   once.Do(onceBody)   fmt.Println("done")}

在上述代码中,当once.Do(onceBody)第一次执行时,会输出"Only once",然后在执行once.Do(onceBody)时会发生死锁,程序无法继续执行下去。

这是因为once.Do()方法在执行过程中会获取互斥锁,在方法内再次调用once.Do()方法,那么就会在获取互斥锁时出现死锁。

因此,我们不能在once.Do方法中再次调用once.Do方法。

4.3 需要对传入的函数进行错误处理

4.3.1 基本说明

一般情况下,如果传入的函数不会出现错误,可以不进行错误处理。但是,如果传入的函数可能出现错误,就必须对其进行错误处理,否则可能会导致程序崩溃或出现不可预料的错误。

因此,在编写传入Once的Do方法的函数时,需要考虑到错误处理问题,保证程序的健壮性和稳定性。

4.3.2 未错误处理导致的问题

下面举一个传入的函数可能出现错误,但是没有对其进行错误处理的例子:

import (   "fmt"   "net"   "sync")var (   initialized bool   connection  net.Conn   initOnce    sync.Once)func initConnection() {   connection, _ = net.Dial("tcp", "err_address")}func getConnection() net.Conn {   initOnce.Do(initConnection)   return connection}func main() {   conn := getConnection()   fmt.Println(conn)   conn.Close()}

在上面例子中,其中initConnection 为传入的函数,用于建立TCP网络连接,但是在sync.Once中执行该函数时,是有可能返回错误的,而这里并没有进行错误处理,直接忽略掉错误。此时调用getConnection 方法,如果initConnection报错的话,获取连接时会返回空连接,后续调用将会出现空指针异常。因此,如果传入sync.Once当中的函数可能发生异常,此时应该需要对其进行处理。

4.3.3 处理方式
  • 3.3.1 panic退出执行

应用程序第一次启动时,此时调用sync.Once来初始化一些资源,此时发生错误,同时初始化的资源是必须初始化的,可以考虑在出现错误的情况下,使用panic将程序退出,避免程序继续执行导致更大的问题。具体代码示例如下:

import (   "fmt"   "net"   "sync")var (   connection  net.Conn   initOnce    sync.Once)func initConnection() {   // 尝试建立连接   connection, err = net.Dial("tcp", "err_address")    if err != nil {       panic("net.Dial error")    }}func getConnection() net.Conn {   initOnce.Do(initConnection)   return connection}

如上,当initConnection方法报错后,此时我们直接panic,退出整个程序的执行。

  • 3.3.2 修改sync.Once实现,Do函数的语意修改为只成功执行一次

在程序运行过程中,可以选择记录下日志或者返回错误码,而不需要中断程序的执行。然后下次调用时再执行初始化的逻辑。这里需要对sync.Once进行改造,原本sync.Once中Do函数的实现为执行一次,这里将其修改为只成功执行一次。具体使用方式需要根据具体业务场景来决定。下面是其中一个实现:

type MyOnce struct {   done int32   m    sync.Mutex}func (o *MyOnce) Do(f func() error) {   if atomic.LoadInt32(&o.done) == 0 {      o.doSlow(f)   }}func (o *MyOnce) doSlow(f func() error) {   o.m.Lock()   defer o.m.Unlock()   if o.done == 0 {      // 只有在函数调用不返回err时,才会设置done      if err := f(); err == nil {         atomic.StoreInt32(&o.done, 1)      }   }}

上述代码中,增加了一个错误处理逻辑。当 f() 函数返回错误时,不会将 done 标记位置为 1,以便下次调用时可以重新执行初始化逻辑。

需要注意的是,这种方式虽然可以解决初始化失败后的问题,但可能会导致初始化函数被多次调用。因此,在编写f() 函数时,需要考虑到这个问题,以避免出现不可预期的结果。

下面是一个简单的例子,使用我们重新实现的Once,展示第一次初始化失败时,第二次调用会重新执行初始化逻辑,并成功初始化:

var (   hasCall bool   conn    net.Conn   m       MyOnce)func initConn() (net.Conn, error) {   fmt.Println("initConn...")   // 第一次执行,直接返回错误   if !hasCall {      return nil, errors.New("init error")   }   // 第二次执行,初始化成功,这里默认其成功   conn, _ = net.Dial("tcp", "baidu.com:80")   return conn, nil}func GetConn() (net.Conn, error) {   m.Do(func() error {      var err error      conn, err = initConn()      if err != nil {         return err      }      return nil   })   // 第一次执行之后,将hasCall设置为true,让其执行初始化逻辑   hasCall = true   return conn, nil}func main() {   // 第一次执行初始化逻辑,失败   GetConn()   // 第二次执行初始化逻辑,还是会执行,此次执行成功   GetConn()   // 第二次执行成功,第三次调用,将不会执行初始化逻辑   GetConn()}

在这个例子中,第一次调用Do方法初始化失败了,done标记位被设置为0。在第二次调用Do方法时,由于done标记位为0,会重新执行初始化逻辑,这次初始化成功了,done标记位被设置为1。第三次调用,由于之前Do方法已经执行成功了,不会再执行初始化逻辑。

关于“go并发利器sync.Once如何使用”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“go并发利器sync.Once如何使用”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注编程网精选频道。

--结束END--

本文标题: go并发利器sync.Once如何使用

本文链接: https://lsjlt.com/news/351553.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • go并发利器sync.Once如何使用
    这篇文章主要介绍了go并发利器sync.Once如何使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇go并发利器sync.Once如何使用文章都会有所收获,下面我们一起来看看吧。1. 简介本文主要介绍 Go ...
    99+
    2023-07-05
  • go并发利器sync.Once使用示例详解
    目录1. 简介2. 基本使用2.1 基本定义2.2 使用方式2.3 使用例子3. 原理4. 使用注意事项4.1 不能将sync.Once作为函数局部变量4.2 不能在once.Do中...
    99+
    2023-03-14
    go并发sync.Once go sync.Once
  • Golang并发利器sync.Once怎么使用
    这篇文章主要介绍了Golang并发利器sync.Once怎么使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Golang并发利器sync.Once怎么使用文章都会有所收获,下面我们一起来看看吧。sync.On...
    99+
    2023-07-06
  • Golang并发利器sync.Once的用法详解
    目录简介sync.Once 基本概念什么是 sync.Oncesync.Once 的应用场景sync.Once 应用实例单例模式延迟初始化sync.Once 实现原理加强的 sync...
    99+
    2023-05-15
    Golang并发sync.Once Golang sync.Once使用 Golang sync.Once
  • Go并发编程之sync.Once使用实例详解
    目录一.序二. 源码分析2.1结构体2.2 接口三. 使用场景案例3.1 单例模式3.2 加载配置文件示例四.总结五. 参考一.序 单从库名大概就能猜出其作用。sync.Once使用...
    99+
    2024-04-02
  • 如何使用Go开发并发程序
    这篇文章主要介绍如何使用Go开发并发程序,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!我们都知道计算机的核心为 CPU,它是计算机的运算和控制核心,承载了所有的计算任务。最近半个世纪...
    99+
    2024-04-02
  • 利用java如何在高并发使用volatile
    利用java如何在高并发使用volatile?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。java 高并发中volatile的实现原理摘要: 在多线程并发编程中synchr...
    99+
    2023-05-31
    java 高并发 volatile
  • Go 中的并发编程:如何使用 PATH 打包并发?
    Go 语言是一门支持并发编程的语言,具有高效和简洁的特性。PATH 是 Go 中的一种并发编程模型,它可以帮助程序员在并发编程过程中更加方便地管理和控制协程的执行。在本文中,我们将探讨如何使用 PATH 打包并发,以及如何编写高效的并发代码...
    99+
    2023-10-01
    path 打包 并发
  • Go 编程:如何使用 PATH 打包并发?
    在 Go 编程中,PATH 是一个非常重要的概念。它是用于设置环境变量的一个列表,其中包含了一系列路径,这些路径指示了操作系统在哪里查找可执行文件。在本篇文章中,我们将深入探讨如何使用 PATH 打包并发。 在 Go 中,我们可以使用 P...
    99+
    2023-10-01
    path 打包 并发
  • 如何使用Go WaitGroup处理并发任务
    在 Go 中使用 `sync.WaitGroup` 来处理并发任务,可以确保在所有任务完成之前等待主程序的执行。下面是一个简单的例子...
    99+
    2023-10-09
    Golang
  • 如何在 Go 中同时使用 PATH 和并发?
    Go 语言作为一门开发效率高、并发能力强的语言,一直备受开发者的喜爱。在 Go 中,使用环境变量 PATH 可以方便地添加需要调用的二进制文件路径,而并发则是 Go 语言最强大的特性之一。那么,如何在 Go 中同时使用 PATH 和并发呢?...
    99+
    2023-10-01
    path 打包 并发
  • Go语言的并发编程,如何更好地利用容器和数组?
    在现代编程中,多线程和并发编程已经成为了非常重要的一部分。随着硬件技术的不断提高,现代计算机系统越来越拥有强大的处理能力,使得多线程和并发编程成为了一种非常有效的编程方式。在Go语言中,我们可以通过使用容器和数组来更好地进行并发编程,本文...
    99+
    2023-11-03
    并发 容器 数组
  • go语言如何并发
    这篇文章主要介绍“go语言如何并发”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“go语言如何并发”文章能帮助大家解决问题。Go语言通过编译器运行时(runtime),从语言上支持了并发的特性;并发是...
    99+
    2023-07-05
  • 如何使用Go开发技术提高Apache并发性能?
    Go语言是一种快速、高效的编程语言,它在网络编程和并发处理方面非常出色。在本文中,我们将介绍如何使用Go语言开发技术来提高Apache的并发性能。 Apache是一个广泛使用的Web服务器,它的并发性能对于高流量的网站来说至关重要。我们可以...
    99+
    2023-08-25
    开发技术 apache 并发
  • 如何使用Go语言和Redis开发高并发系统
    如何使用Go语言和Redis开发高并发系统引言:随着互联网的快速发展,高并发系统的需求也越来越大。在这样的背景下,Go语言和Redis作为高性能的工具,成为了众多开发者的首选。本文将介绍如何使用Go语言和Redis开发高并发系统,包括详细的...
    99+
    2023-10-26
    Go语言 redis 高并发系统
  • 如何使用Go编写高效的并发算法?
    Go 是一种高效的编程语言,具有出色的并发处理能力。在编写高效的并发算法时,使用 Go 是一个明智的选择。在本文中,我们将介绍如何使用 Go 编写高效的并发算法。 一、Go 的并发处理能力 Go 是一种开发并发应用程序的理想语言。Go 语言...
    99+
    2023-06-21
    并发 ide npm
  • 如何在 Go 中使用 PATH 打包并发程序?
    Go 语言是一种现代化的编程语言,它在并发编程方面非常出色。而在并发编程中,如何使用 PATH 打包程序是一个非常重要的话题。在本文中,我们将探讨如何在 Go 中使用 PATH 打包并发程序。 什么是 PATH? PATH 是 Go 语言中...
    99+
    2023-10-01
    path 打包 并发
  • C++并发编程:如何利用多核CPU实现并发?
    c++++ 并发编程通过创建线程、互斥锁和条件变量来充分利用多核 cpu 的优势。创建线程允许任务并行执行。互斥锁充当锁,确保共享数据不会被多个线程同时访问,从而避免数据损坏。条件变量用...
    99+
    2024-05-01
    c++ 并发编程 并发访问
  • 如何利用GO和JavaScript实现高效的重定向和并发?
    随着Web应用程序的发展,用户对速度的要求也越来越高。为了提高用户体验,网站管理员需要确保网站的响应速度快,同时尽可能地减少服务器的负载。在此过程中,重定向和并发成为了两个重要的优化技术。 本文将介绍如何使用GO和JavaScript实现...
    99+
    2023-07-19
    javascript 重定向 并发
  • Go开发-使用Goroutine如何控制HTTP请求的并发量
    一、明确需求 我们使用 go 并发调用接口发起 HTTP 请求时,只需要在 func() 前面加上 go 关键字就很容易完成了,就是因为让并发变得如此简单,所以有的时候我们就需要控制一下并发请求的数量。 现在有个需求:本地有一千万条手机号,...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作