847 words
4 minutes

Go语言教程:深入浅出sync.Once

在Go语言中,sync.Once是一个非常有用的工具,它可以确保某个操作只执行一次。无论有多少个goroutine调用它,操作都只会执行一次。本文将通过几个示例代码,深入浅出地介绍sync.Once的使用和原理。

示例1:基本使用#

首先,我们来看一个简单的例子:

package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var counter uint32
var once sync.Once
once.Do(func() {
atomic.AddUint32(&counter, 1)
})
fmt.Printf("The counter: %d\\n", counter)
once.Do(func() {
atomic.AddUint32(&counter, 2)
})
fmt.Printf("The counter: %d\\n", counter)
}

解释#

  1. 定义变量:我们定义了一个uint32类型的变量counter和一个sync.Once类型的变量once
  2. 第一次调用**once.Do**:我们传入一个匿名函数,该函数将counter加1。由于这是第一次调用once.Do,所以这个操作会执行。
  3. 第二次调用**once.Do**:我们再次传入一个匿名函数,这次将counter加2。然而,由于sync.Once的特性,这个操作不会执行。

输出结果:

The counter: 1
The counter: 1

可以看到,第二次调用once.Do并没有改变counter的值。

示例2:多goroutine并发#

接下来,我们来看一个多goroutine并发的例子:

package main
import (
"fmt"
"sync"
"time"
)
func main() {
var once sync.Once
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
once.Do(func() {
for i := 0; i < 3; i++ {
fmt.Printf("Do task. [1-%d]\\n", i)
time.Sleep(time.Second)
}
})
fmt.Println("Done. [1]")
}()
go func() {
defer wg.Done()
time.Sleep(time.Millisecond * 500)
once.Do(func() {
fmt.Println("Do task. [2]")
})
fmt.Println("Done. [2]")
}()
go func() {
defer wg.Done()
time.Sleep(time.Millisecond * 500)
once.Do(func() {
fmt.Println("Do task. [3]")
})
fmt.Println("Done. [3]")
}()
wg.Wait()
}

解释#

  1. 定义变量:我们定义了一个sync.Once类型的变量once和一个sync.WaitGroup类型的变量wg
  2. 添加goroutine:我们启动了三个goroutine,每个goroutine都会调用once.Do
  3. 执行任务:只有第一个goroutine中的任务会被执行,其他两个goroutine中的任务不会被执行。

输出结果:

Do task. [1-0]
Do task. [1-1]
Do task. [1-2]
Done. [1]
Done. [2]
Done. [3]

可以看到,只有第一个goroutine中的任务被执行了,其他两个goroutine中的任务没有被执行。

示例3:处理panic#

最后,我们来看一个处理panic的例子:

package main
import (
"errors"
"fmt"
"sync"
"time"
)
func main() {
var once sync.Once
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
defer func() {
if p := recover(); p != nil {
fmt.Printf("fatal error: %v\\n", p)
}
}()
once.Do(func() {
fmt.Println("Do task. [4]")
panic(errors.New("something wrong"))
fmt.Println("Done. [4]")
})
}()
go func() {
defer wg.Done()
time.Sleep(time.Millisecond * 500)
once.Do(func() {
fmt.Println("Do task. [5]")
})
fmt.Println("Done. [5]")
}()
wg.Wait()
}

解释#

  1. 定义变量:我们定义了一个sync.Once类型的变量once和一个sync.WaitGroup类型的变量wg
  2. 添加goroutine:我们启动了两个goroutine,第一个goroutine会触发panic,第二个goroutine会等待500毫秒后调用once.Do
  3. 处理**panic**:第一个goroutine中的任务会触发panic,我们使用recover来捕获并处理这个panic

输出结果:

Do task. [4]
fatal error: something wrong
Done. [5]

可以看到,第一个goroutine中的任务触发了panic,但第二个goroutine中的任务仍然没有被执行。

总结#

通过以上三个示例,我们可以看到sync.Once的几个关键特性:

  1. 确保操作只执行一次:无论有多少个goroutine调用once.Do,操作都只会执行一次。
  2. 线程安全sync.Once是线程安全的,可以在多个goroutine中并发使用。
  3. 处理**panic**:即使在once.Do中触发了panicsync.Once也不会再次执行操作。

希望通过这些示例,您能更好地理解和使用sync.Once

Share

If this article helped you, please share it with others!

Go语言教程:深入浅出sync.Once
https://blog.ithuo.net/posts/go-tutorial-understanding-sync-once-with-examples/
Author
Derick
Published at
2022-05-31
License
CC BY-NC-SA 4.0
Last updated on 2022-05-31,1338 days ago

Some content may be outdated

Table of Contents