偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Go Singleton 模式的實(shí)現(xiàn)

開發(fā) 前端
在日常開發(fā)中,我們經(jīng)常會(huì)用到單例模式,比如啟動(dòng)時(shí)創(chuàng)建一個(gè)訪問數(shù)據(jù)庫(kù)的客戶端,或者運(yùn)行時(shí)創(chuàng)建一個(gè)訪問第三方服務(wù)的客戶端。啟動(dòng)時(shí)的單例問題比較簡(jiǎn)單,因?yàn)椴淮嬖诓l(fā)問題,直接在 main 函數(shù)創(chuàng)建即可。

背景

在日常開發(fā)中,我們經(jīng)常會(huì)用到單例模式,比如啟動(dòng)時(shí)創(chuàng)建一個(gè)訪問數(shù)據(jù)庫(kù)的客戶端,或者運(yùn)行時(shí)創(chuàng)建一個(gè)訪問第三方服務(wù)的客戶端。啟動(dòng)時(shí)的單例問題比較簡(jiǎn)單,因?yàn)椴淮嬖诓l(fā)問題,直接在 main 函數(shù)創(chuàng)建即可。

package main
func main() {
    Init()
}

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

如果創(chuàng)建失敗則直接 panic 重新啟動(dòng)服務(wù)。而運(yùn)行時(shí)的單例問題情況有所不同,我們一般會(huì)使用 sync.Once 來實(shí)現(xiàn)。

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í)行一次,但是這個(gè)只執(zhí)行一次也會(huì)帶來一個(gè)問題,那就是如果執(zhí)行失敗了再也不會(huì)再執(zhí)行了。下面是 go1.24.3 中 sync.Once 的實(shí)現(xiàn)。

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 都會(huì)設(shè)置 done 為 1,所以如果 f 創(chuàng)建客戶端失敗,那么后面也不會(huì)調(diào)用了。但是存在這樣的場(chǎng)景,我們?cè)谔幚碚?qǐng)求時(shí)會(huì)創(chuàng)建一個(gè)單例去做一些事情,但因?yàn)檫@不是關(guān)鍵路徑,所以執(zhí)行失敗時(shí)不能 panic,而是返回錯(cuò)誤并希望下次還能重新走這個(gè)流程。所以我們需要實(shí)現(xiàn)一個(gè)單例模式,每次按需實(shí)時(shí)獲取,并且可以保證創(chuàng)建失敗時(shí)還可以重新執(zhí)行創(chuàng)建流程。

實(shí)現(xiàn) 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
}

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

實(shí)現(xiàn) 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 的一開始先判斷是否已經(jīng)創(chuàng)建過了,如果是則直接返回,避免了加鎖,這個(gè)看起來解決了問題,但是同時(shí)帶來了一個(gè)比較隱晦的問題,這種方式無(wú)法保證內(nèi)存可見性,也就是說當(dāng)讀者看到 s.instance 非空時(shí),不代表 s.instance 指向的實(shí)例是完成的,即初始化完成的。看一個(gè)例子。

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 內(nèi)容立即對(duì)其他線程可見
        }()

        // 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í)行上面的代碼,最終會(huì)輸出 flag is true,說明 ptr 非空時(shí),ptr.Field 卻是 0?;氐絾卫龑?shí)現(xiàn)的代碼中測(cè)試也是存在類似的問題。

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")
        }
    }
}

上面的代碼最終會(huì)輸出 singleton should not be nil。說明當(dāng) singleton.Get 觀察到 s.instance 非空時(shí) s.instance 指向到單例對(duì)象并沒有完成構(gòu)造。

實(shí)現(xiàn) 3

為了實(shí)現(xiàn)內(nèi)存的可見性,我們需要使用 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 會(huì)保證 Store 之前的寫入全部可見,也就是說當(dāng) Load 返回非空指針時(shí),Store 寫入的指針以及 s.factory 構(gòu)造的結(jié)構(gòu)體已經(jīng)全部同步完成。具體可以參考這里 https://github.com/theanarkh/singleton。

責(zé)任編輯:武曉燕 來源: 編程雜技
相關(guān)推薦

2009-08-25 18:04:30

C#實(shí)現(xiàn)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實(shí)現(xiàn)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)設(shè)計(jì)設(shè)計(jì)模式

2009-08-12 11:40:39

雙檢測(cè)鎖定

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

設(shè)計(jì)模式訪客模式

2023-05-04 08:47:31

命令模式抽象接口

2023-05-26 08:41:23

模式Go設(shè)計(jì)模式

2023-12-29 08:10:41

Go并發(fā)開發(fā)

2013-05-28 09:43:38

GoGo語(yǔ)言并發(fā)模式

2010-01-21 17:48:25

VB.NET Sing

2023-05-15 08:51:46

解釋器模式定義
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)