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

Go Singleton 模式的實現

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

背景

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

package main
func main() {
    Init()
}

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

如果創(chuàng)建失敗則直接 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 可以保證并發(fā)情況下只執(zhí)行一次,但是這個只執(zhí)行一次也會帶來一個問題,那就是如果執(zhí)行失敗了再也不會再執(zhí)行了。下面是 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 是否執(zhí)行成功,Go 都會設置 done 為 1,所以如果 f 創(chuàng)建客戶端失敗,那么后面也不會調用了。但是存在這樣的場景,我們在處理請求時會創(chuàng)建一個單例去做一些事情,但因為這不是關鍵路徑,所以執(zhí)行失敗時不能 panic,而是返回錯誤并希望下次還能重新走這個流程。所以我們需要實現一個單例模式,每次按需實時獲取,并且可以保證創(chuàng)建失敗時還可以重新執(zhí)行創(chuàng)建流程。

實現 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
}

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

實現 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 的一開始先判斷是否已經創(chuàng)建過了,如果是則直接返回,避免了加鎖,這個看起來解決了問題,但是同時帶來了一個比較隱晦的問題,這種方式無法保證內存可見性,也就是說當讀者看到 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
        }
    }

}

執(zhí)行上面的代碼,最終會輸出 flag is true,說明 ptr 非空時,ptr.Field 卻是 0。回到單例實現的代碼中測試也是存在類似的問題。

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 單態(tài) 模式

2012-08-22 10:10:25

單態(tài)單態(tài)設計設計模式

2009-08-12 11:40:39

雙檢測鎖定

2009-08-12 13:22:44

Singleton模式

2023-03-27 00:20:48

2021-07-12 10:24:36

Go裝飾器代碼

2023-05-04 08:47:31

命令模式抽象接口

2023-04-10 09:20:13

設計模式訪客模式

2023-05-26 08:41:23

模式Go設計模式

2010-01-21 17:48:25

VB.NET Sing

2013-05-28 09:43:38

GoGo語言并發(fā)模式

2023-05-15 08:51:46

解釋器模式定義

2023-12-29 08:10:41

Go并發(fā)開發(fā)
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: av在线播放免费 | 午夜电影网| 久久久久久国产精品三区 | 中文久久| 中文字幕日韩欧美一区二区三区 | 国产一区二区三区在线看 | 日本超碰在线 | 日韩图区 | 中文字幕在线视频观看 | 精品一区二区三区电影 | 黑人久久久 | 欧美日韩在线不卡 | 亚洲成色777777在线观看影院 | av大全在线 | 欧美中文在线 | 午夜精品久久 | 99这里只有精品视频 | 成人午夜毛片 | 日韩欧美综合 | 久久精品手机视频 | 精品国产一二三区 | 岛国二区 | 黄色在线网站 | 久久91精品国产一区二区三区 | 国产偷录叫床高潮录音 | 看片一区| 午夜视频在线观看视频 | 天天操网 | a毛片视频网站 | 国产精品久久久久久久久久久久 | 精品国产欧美日韩不卡在线观看 | 久久久久久黄 | 日韩在线视频观看 | 99热这里有精品 | 三级免费网 | 国产欧美精品在线 | 久久亚洲一区二区 | 亚洲一区二区在线播放 | 精品自拍视频在线观看 | 国产视频线观看永久免费 | 亚洲天堂一区 |