成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

簡潔而不簡單的 sync.Once,你學會了嗎?

開發 前端
sync.Once? 的源代碼只有短短十幾行,看似簡單的條件分支背后充斥著 并發執行?, 原子操作?, 同步原語 等基礎原理, 深入理解這些原理之后,可以幫助我們更好地構建并發系統,解決并發編程中遇到的問題。

概述

sync.Once? 可以保證在運行期間的某段程序只會執行一次,典型的使用場景有 初始化配置?, 數據庫連接 等。

圖片

sync.Once 流程圖

與 init 函數差異

  • ? init 函數是當所在的 package 首次被加載時執行,若遲遲未被使用,則既浪費了內存,又延長了程序加載時間
  • ? sync.Once 方法可以在代碼的任意位置初始化和調用,并發場景下是線程安全的,因此可以延遲到使用時再調用 (懶加載)

示例

通過一個小例子展示 sync.Once 的使用方法。

package main

import (
"fmt"
"sync"
)


// 數據庫配置
type Config struct {
Server string
Port int
}

var (
once sync.Once
config *Config
)


// 初始化數據庫配置
func InitConfig() *Config {
once.Do(func() {
fmt.Println("mock init ...") // 模擬初始化代碼
})

return config
}

func main() {
// 連續調用 5 次初始化方法
for i := 0; i < 5; i++ {
_ = InitConfig()
}
}
$ go run main.go

# 輸出如下
mock init ...

從輸出的結果中可以看到,雖然我們調用了 5 次初始化配置方法,但是真正的初始化方法只執行了 1 次,實現了設計模式中 單例模式 的效果。

圖片

方法調用結果

內部實現

接下來,我們來探究一下 sync.Once? 的內部實現,文件路徑為 $GOROOT/src/sync/once.go?,筆者的 Go 版本為 go1.19 linux/amd64。

Once 結構體

package sync

import (
"sync/atomic"
)

// Once 是一個只執行一次操作的對象
// Once 一旦使用后,便不能再復制
//
// 在 Go 內存模型術語中,once.Do(f) 中函數 f 的返回值會在 once.Do() 函數返回前完成同步
type Once struct {
done uint32
m Mutex
}

sync.Once? 的結構體有 2 個字段,m? 表示持有一個互斥鎖,這是并發調用場景下 只執行一次? 的保證, done? 字段表示調用是否已完成,使用的字段類型是 uint32?, 這樣就可以使用標準庫中 atomic? 包里面 *Uint32 系列方法了,

為什么沒有使用 bool? 類型呢? 因為標準庫中 atomic? 包并未提供針對 bool? 類型的相關方法,如果適用 bool? 類型,操作時就需要轉換為 指針? 類型, 然后使用 atomic.*Pointer? 系列方法操作,這樣會造成內存占用過多 (bool? 占用 1 個字節,指針 占用 8 個字節) 和性能損耗 (參數類型轉換)。

done 字段

圖片

sync.Once 結構體

done 作為結構體的第一個字段,能夠減少 CPU 指令,也就是能夠提升性能,具體來說:

熱路徑 hot path? 是程序非常頻繁執行的一系列指令,sync.Once? 絕大部分場景都會訪問 done? 字段,所以 done? 字段是處于 hot path? 上的,這樣一來 hot path 編譯后的機器碼指令更少,性能更高。

為什么放在第一個字段就能夠減少指令呢?因為結構體第一個字段的地址和結構體的指針是相同的,如果是第一個字段,直接對結構體的指針解引用即可。如果是其他的字段,除了結構體指針外,還需要計算與第一個值的 偏移量。在機器碼中,偏移量是隨指令傳遞的附加值,CPU 需要做一次偏移值與指針的加法運算, 才能獲取要訪問的值的地址,因此訪問第一個字段的機器碼更緊湊,速度更快。

Do 方法

// 當且僅當第一次調用實例 Once 的 Do 方法時,Do 去調用函數 f
// 換句話說,調用 once.Do(f) 多次時,只有第一次調用會調用函數 f,即使 f 函數在每次調用中有不同的參數值

// 并發調用 Do 函數時,需要等到其中的一個函數 f 執行之后才會返回
// 所以函數 f 中不能調用同一個 once 實例的 Do 函數 (遞歸調用),否則會發生死鎖
// 如果函數 f 內部 panic, Do 函數同樣認為其已經返回,將來再次調用 Do 函數時,將不再執行函數 f
// 所以這就要求我們寫出健壯的 f 函數
func (o *Once) Do(f func()) {
// 下面是一個錯誤的實現
// if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
// f()
// }

// 錯誤原因分析:
// 這里以數據庫連接場景為例,在并發調用情況下,假設其中 1 個 goroutine 正在執行函數 f (初始化連接)
// 此時其他的 goroutine 將不會等待這個 goroutine 執行完成,而是會直接返回,
// 如果連接發生了一些延遲,導致函數 f 還未執行完成,那么此時連接其實還未建立,
// 但是其他的 goroutine 認為函數 f 已經執行完成,連接已建立,可以開始使用了
// 最后當其他 goroutine 使用未建立的連接操作時,產生報錯

// 要解決上面的問題, 就需要確保當前函數返回時, 函數 f 已經執行完成,
// 這就是 slow path 退回到互斥鎖的原因,以及為什么 atomic.StoreUint32 需要延遲到函數 f 返回之后
if atomic.LoadUint32(&o.done) == 0 {
o.doSlow(f) // slow-path 允許內聯
}
}

圖片

錯誤實現示例

doSlow 方法

func (o *Once) doSlow(f func()) {
// 并發場景下,可能會有多個 goroutine 執行到這里
o.m.Lock() // 但是只有 1 個 goroutine 能獲取到互斥鎖
defer o.m.Unlock()

// 注意下面臨界區內的判斷和修改

// 在 atomic.LoadUint32 時為 0 ,不等于獲取到鎖之后也是 0,所以需要二次檢測
// 因為已經獲取到互斥鎖,根據 Go 的同步原語約束,對于字段 done 的修改需要在獲取到互斥鎖之前同步
// 所以這里直接訪問字段即可,不需要調用 atomic.LoadUint32 方法
// 如果有其他 goroutine 已經修改了字段 done,那么就不會進入條件分支,沒有任何影響
if o.done == 0 {
// 只要函數 f 成功執行過一次,就將 o.done 修改為 1
// 這樣其他 goroutine 就不會再執行了,從而保證了函數 f() 只會執行一次,
// 這里必須使用 atomic.StoreUint32 方法來滿足 Go 的同步原語約束
defer atomic.StoreUint32(&o.done, 1)
f()
}
}

圖片

正確實現示例

小結

sync.Once? 的源代碼只有短短十幾行,看似簡單的條件分支背后充斥著 并發執行?, 原子操作?, 同步原語 等基礎原理, 深入理解這些原理之后,可以幫助我們更好地構建并發系統,解決并發編程中遇到的問題。

Reference

  1. 1. Go sync.Once[1]

引用鏈接

[1]? Go sync.Once: https://geektutu.com/post/hpg-sync-once.html

責任編輯:武曉燕 來源: 洋芋編程
相關推薦

2024-06-05 11:06:22

Go語言工具

2021-08-29 18:13:03

緩存失效數據

2023-06-06 08:28:58

Sync.OnceGolang

2024-09-09 09:00:12

架構設計算法

2022-07-08 09:27:48

CSSIFC模型

2024-01-19 08:25:38

死鎖Java通信

2024-02-04 00:00:00

Effect數據組件

2023-07-26 13:11:21

ChatGPT平臺工具

2023-01-10 08:43:15

定義DDD架構

2024-02-02 11:03:11

React數據Ref

2023-08-01 12:51:18

WebGPT機器學習模型

2024-01-02 12:05:26

Java并發編程

2023-06-06 07:50:07

權限管理hdfsacl

2023-10-10 11:04:11

Rust難點內存

2024-05-06 00:00:00

InnoDBView隔離

2023-01-30 09:01:54

圖表指南圖形化

2024-07-31 08:39:45

Git命令暫存區

2023-12-12 08:02:10

2024-08-06 09:47:57

2022-11-08 08:45:30

Prettier代碼格式化工具
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 99精品视频免费观看 | 欧美日韩大片 | 精品无码三级在线观看视频 | 久久国产精品99久久久久久丝袜 | 日韩高清一区二区 | 日韩一二区 | 日本a∨精品中文字幕在线 亚洲91视频 | 精品欧美一区二区三区久久久 | 中文字幕1区2区 | 国产成人综合亚洲欧美94在线 | 九九久久久久久 | 久热久热 | 69福利影院| 久久99精品久久久久 | 亚洲福利在线观看 | 日韩男人天堂 | 国产精品一二三区 | 久久精品国产亚洲夜色av网站 | 欧美亚洲第一区 | 日韩精品一区二区三区在线观看 | 欧美一区二 | 香蕉国产在线视频 | 五月网婷婷 | 久草成人网 | 影音先锋成人资源 | 一级毛片色一级 | 欧美在线一区二区三区 | 亚洲成人黄色 | 欧美高清dvd | 日韩欧美在线观看视频网站 | 亚洲三区视频 | 在线国产视频观看 | 亚洲精品91 | 国产午夜三级一区二区三 | 激情一区二区三区 | 国产一区二区三区久久久久久久久 | 看av片网站 | 久久亚洲一区二区三区四区 | 美女逼网站 | 免费观看黄色一级片 | 中文字幕在线播放第一页 |