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

Go Singleton 模式的實現

開發 前端
在日常開發中,我們經常會用到單例模式,比如啟動時創建一個訪問數據庫的客戶端,或者運行時創建一個訪問第三方服務的客戶端。啟動時的單例問題比較簡單,因為不存在并發問題,直接在 main 函數創建即可。

背景

在日常開發中,我們經常會用到單例模式,比如啟動時創建一個訪問數據庫的客戶端,或者運行時創建一個訪問第三方服務的客戶端。啟動時的單例問題比較簡單,因為不存在并發問題,直接在 main 函數創建即可。

package main
func main() {
    Init()
}

var client *Client
func Init() {
    cilent, err = New(...) 
    if err != nil {
        panic(err)
    }
}

如果創建失敗則直接 panic 重新啟動服務。而運行時的單例問題情況有所不同,我們一般會使用 sync.Once 來實現。

var client *Client
var once sync.Once

func GetClient() Client {
    once.Do(func() {
        cilent, err = New(...) 
        if err != nil {
            //
        }
    })
    return client
}

雖然 sync.Once 可以保證并發情況下只執行一次,但是這個只執行一次也會帶來一個問題,那就是如果執行失敗了再也不會再執行了。下面是 go1.24.3 中 sync.Once 的實現。

type Once struct {
    done atomic.Uint32
    m    Mutex
}

func (o *Once) Do(f func()) {
    if o.done.Load() == 0 {
        o.doSlow(f)
    }
}

func (o *Once) doSlow(f func()) {
    o.m.Lock()
    defer o.m.Unlock()
    if o.done.Load() == 0 {
        defer o.done.Store(1)
        f()
    }
}

可以看到不管 f 是否執行成功,Go 都會設置 done 為 1,所以如果 f 創建客戶端失敗,那么后面也不會調用了。但是存在這樣的場景,我們在處理請求時會創建一個單例去做一些事情,但因為這不是關鍵路徑,所以執行失敗時不能 panic,而是返回錯誤并希望下次還能重新走這個流程。所以我們需要實現一個單例模式,每次按需實時獲取,并且可以保證創建失敗時還可以重新執行創建流程。

實現 1

type Singleton[T any] struct {
        mu     sync.Mutex
        loaded bool

        loader func() T
        data   T
}

func (in *Singleton[T]) Get() T {
        in.mu.Lock()
        if !in.loaded && in.loader != nil {
                in.mu.Unlock()
                in.Set(in.loader())
                in.mu.Lock()
        }
        defer in.mu.Unlock()
        return in.data
}

func (in *Singleton[T]) Set(data T) {
        in.mu.Lock()
        defer in.mu.Unlock()
        in.loaded = true
        in.data = data
}

這種方式實現的思路比較清晰簡單,但是性能相對來說不太好,因為第一個創建成功后后續每次獲取時都需要加鎖,如果并發量大的會引起一定時間的代碼阻塞。所以嘗試優化這部分的邏輯。

實現 2

type F[T any] func() (*T, error)

type singleton[T any] struct {
    factory  F[T]
    instance *T
    mutex    sync.Mutex
}

func (s *singleton[T]) Get() (*T, error) {
    if s.instance != nil {
        return s.instance, nil
    }
    s.mutex.Lock()
    defer s.mutex.Unlock()
    if s.instance != nil {
        return s.instance, nil
    }
    result, err := s.factory()
    if err != nil {
        returnnil, err
    }
    s.instance = result
    return result, nil
}

在 Get 的一開始先判斷是否已經創建過了,如果是則直接返回,避免了加鎖,這個看起來解決了問題,但是同時帶來了一個比較隱晦的問題,這種方式無法保證內存可見性,也就是說當讀者看到 s.instance 非空時,不代表 s.instance 指向的實例是完成的,即初始化完成的??匆粋€例子。

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {
    type MyStruct struct {
        Field int
    }
    for {
        var flag atomic.Bool
        var ptr *MyStruct
        var wg sync.WaitGroup
        wg.Add(2)

        // Goroutine A
        gofunc() {
            defer wg.Done()
            data := &MyStruct{Field: 42}
            ptr = data // 原子操作,但不保證 data 內容立即對其他線程可見
        }()

        // Goroutine B
        gofunc() {
            defer wg.Done()
            if ptr != nil {
                field := ptr.Field
                if field == 0 {
                    fmt.Println(field)
                    flag.Store(true)
                }
            }
        }()
        wg.Wait()
        if flag.Load() {
            println("flag is true")
            break
        }
    }

}

執行上面的代碼,最終會輸出 flag is true,說明 ptr 非空時,ptr.Field 卻是 0?;氐絾卫龑崿F的代碼中測試也是存在類似的問題。

package singleton

import (
    "sync"
    "testing"
)

type Dummy struct {
    Ptr *string
}

func factory() (*Dummy, error) {
    ptr := "test"
    return &Dummy{
        Ptr: &ptr,
    }, nil
}

func TestConcurrent(t *testing.T) {
    for {
        singleton := New(factory)
        var flag bool
        var ptr *Dummy
        var wg sync.WaitGroup
        len := 10
        wg.Add(len)
        for i := 0; i < len; i++ {
            gofunc() {
                defer wg.Done()
                ptr, _ = singleton.Get()
                if ptr.Ptr == nil {
                    flag = true
                }
            }()
        }
        wg.Wait()
        if flag {
            t.Fatal("singleton should not be nil")
        }
    }
}

上面的代碼最終會輸出 singleton should not be nil。說明當 singleton.Get 觀察到 s.instance 非空時 s.instance 指向到單例對象并沒有完成構造。

實現 3

為了實現內存的可見性,我們需要使用 Go 提供的 API。

package singleton

import (
    "sync"
    "sync/atomic"
)

type F[T any] func() (*T, error)

type singleton[T any] struct {
    factory  F[T]
    instance atomic.Pointer[T]
    mutex    sync.Mutex
}

func (s *singleton[T]) Get() (*T, error) {
    if s.instance.Load() != nil {
        return s.instance.Load(), nil
    }
    s.mutex.Lock()
    defer s.mutex.Unlock()
    if s.instance.Load() != nil {
        return s.instance.Load(), nil
    }
    result, err := s.factory()
    if err != nil {
        returnnil, err
    }
    s.instance.Store(result)
    return result, nil
}

上面代碼中,Load 會保證 Store 之前的寫入全部可見,也就是說當 Load 返回非空指針時,Store 寫入的指針以及 s.factory 構造的結構體已經全部同步完成。具體可以參考這里 https://github.com/theanarkh/singleton。

責任編輯:武曉燕 來源: 編程雜技
相關推薦

2009-08-25 18:04:30

C#實現Singlet

2009-07-08 17:25:05

Java Single

2009-07-09 17:30:59

Singleton模式C++ SingletJava Single

2009-08-31 16:12:02

C#使用Singlet

2010-01-07 17:51:36

VB.NET實現Sin

2009-08-31 15:48:02

C# Singleto

2009-09-02 16:23:27

C# Singleto

2011-07-18 16:51:51

Cocoa 單態 模式

2012-08-22 10:10:25

單態單態設計設計模式

2009-08-12 11:40:39

雙檢測鎖定

2023-03-27 00:20:48

2009-08-12 13:22:44

Singleton模式

2021-07-12 10:24:36

Go裝飾器代碼

2023-04-10 09:20:13

設計模式訪客模式

2023-05-04 08:47:31

命令模式抽象接口

2023-05-26 08:41:23

模式Go設計模式

2023-12-29 08:10:41

Go并發開發

2013-05-28 09:43:38

GoGo語言并發模式

2010-01-21 17:48:25

VB.NET Sing

2023-05-15 08:51:46

解釋器模式定義
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日本视频免费观看 | 草久久| 久久97精品| 一级片片 | 日本三级网站在线观看 | 日本高清不卡视频 | 射欧美 | 97色在线视频 | 国产91久久久久久 | 伊人免费在线观看 | 91一区二区| 九九综合 | 国产高清一二三区 | 国产精品一区一区 | 夜夜爽99久久国产综合精品女不卡 | www.一区二区三区.com | 国产精品免费一区二区三区四区 | 免费的一级视频 | 91精品久久久久久综合五月天 | 在线观看免费黄色片 | 91久久国产 | 五月天天丁香婷婷在线中 | 成人精品一区二区三区 | 欧美日韩手机在线观看 | www.天天操 | 日本黄色一级视频 | 日本一区二区三区精品视频 | 婷婷久久五月 | 午夜视频在线免费观看 | 亚洲精品成人av久久 | 国产高清精品一区二区三区 | 黄色免费三级 | 天堂亚洲 | 亚洲免费在线观看 | 国产1页| 亚洲精品久久久久久一区二区 | 久久久精品综合 | wwwxx在线观看 | 亚洲一区二区 | 久久久久中文字幕 | 亚洲福利 |