最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文詳解下劃線字段在golang結(jié)構(gòu)體中的應(yīng)用

 更新時(shí)間:2025年08月18日 08:16:07   作者:apocelipes  
這篇文章主要為大家詳細(xì)介紹了下劃線字段在golang結(jié)構(gòu)體中應(yīng)用的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

最近公司里的新人問(wèn)了我一個(gè)問(wèn)題:這段代碼是啥意思。這個(gè)問(wèn)題很普通也很常見(jiàn),我還是個(gè)新人的時(shí)候也經(jīng)常問(wèn),當(dāng)然,現(xiàn)在我不是新人了但我也經(jīng)常發(fā)出類(lèi)似的提問(wèn)。

代碼是長(zhǎng)這樣的:

type BussinessObject struct {
    _      [0]func()
    ID     uint64
    FieldA string
    FieldB *int64
    ...
}

新人問(wèn)我_ [0]func()是什么。不得不說(shuō)這是個(gè)好問(wèn)題,因?yàn)檫@樣的代碼第一眼看上去誰(shuí)都會(huì)覺(jué)得很奇怪,這種叫沒(méi)有名字只有一個(gè)下劃線占位符的我們暫且叫做“下劃線字段”,下劃線字段會(huì)占用實(shí)際的空間但又不能被訪問(wèn),使用這樣一個(gè)字段有什么用呢?

今天我就來(lái)講講下劃線字段在Golang中的實(shí)際應(yīng)用,除了能回答上面新人的疑問(wèn),還能幫你了解一些開(kāi)源項(xiàng)目中的golang慣用法。

使結(jié)構(gòu)體不能被比較

默認(rèn)情況下golang的結(jié)構(gòu)體是可以進(jìn)行相等和不等判斷的,編譯器會(huì)自動(dòng)生成比較每個(gè)字段的值的代碼。

這和其他語(yǔ)言是很不一樣的,在c語(yǔ)言里想要比較兩個(gè)結(jié)構(gòu)體你需要自寫(xiě)比較函數(shù)或者借助memcmp等標(biāo)準(zhǔn)庫(kù)接口,在c++/Java/python中則需要重載/重寫(xiě)指定的運(yùn)算符或者方法,而在go里除了少數(shù)特殊情況之外這些工作都由編譯器代勞了。

然而天下沒(méi)有免費(fèi)的午餐,讓編譯器代勞等價(jià)于失去對(duì)比較操作的控制權(quán)。

舉個(gè)簡(jiǎn)單的例子,你有一個(gè)字段都是指針類(lèi)型的結(jié)構(gòu)體,這些結(jié)構(gòu)體可以進(jìn)行等值判斷,判斷的依據(jù)是指針指向的實(shí)際內(nèi)容:

type A struct {
    Name *string
    Age  int
}

這種結(jié)構(gòu)體在JSON序列化和數(shù)據(jù)庫(kù)操作中很常見(jiàn),理想中的判斷操作應(yīng)該是先解引用Name,比較他們指向的字符串的值,然后再比較Age是否相同。

但編譯器生成的是先比較Name存儲(chǔ)的地址值而不是他們指向的字符串的具體內(nèi)容,然后再比較Age。這樣當(dāng)你使用==來(lái)處理結(jié)構(gòu)體的時(shí)候就會(huì)得到錯(cuò)誤的結(jié)果:

func (a *A) Equal(b *A) bool {
    if b == nil || a.Name == nil || b.Name == nil {
        return false
    }
    return *a.Name == *b.Name && a.Age == b.Age
}

//go:noinline
func getString(s string) *string {
    buff := strings.Builder{}
    buff.WriteString(s)
    result := buff.String()
    return &result
}

func main() {
    a := A{getString("test"), 100}
    b := A{getString("test"), 100}
    fmt.Println(a == b, (*A).Equal(&a, &b)) // false, true
}

函數(shù)getString模擬了序列化和反序列化時(shí)的場(chǎng)景:相同內(nèi)容的字符串每次都是獨(dú)立分配的,導(dǎo)致了他們的地址不同。從結(jié)果可以看到golang默認(rèn)生成的比較是不正確。

更糟糕的是這個(gè)默認(rèn)生成的行為無(wú)法禁止,會(huì)導(dǎo)致==的誤用。

實(shí)際生產(chǎn)中還有另一種情況,編譯器覺(jué)得結(jié)構(gòu)體符合比較的規(guī)則,但邏輯上這種結(jié)構(gòu)體的等值比較沒(méi)有實(shí)際意義。顯然放任編譯器的默認(rèn)行為沒(méi)有任何好處。

這時(shí)候新人問(wèn)的那行代碼就發(fā)揮用處了,我們把那行代碼加進(jìn)結(jié)構(gòu)體里:

type A struct {
    _    [0]func()
    Name *string
    Age  int
}

現(xiàn)在程序會(huì)報(bào)錯(cuò)了:invalid operation: a == b (struct containing [0]func() cannot be compared)。

這就是之前說(shuō)的少數(shù)幾種特殊情況:函數(shù)、切片、map是不能比較的,包含這些類(lèi)型字段的結(jié)構(gòu)體或者數(shù)組也不可以進(jìn)行比較操作。

我們的下劃線字段是一個(gè)元素為函數(shù)的數(shù)組。在Go中,數(shù)組可以進(jìn)行等值比較,但函數(shù)不能,因此[0]func()類(lèi)型的下劃線
字段將無(wú)法參與比較。接著由于go語(yǔ)法的規(guī)定,只要有一個(gè)字段不能進(jìn)行比較,那么整個(gè)結(jié)構(gòu)體也不能,所以==不再能應(yīng)用在結(jié)構(gòu)體A上。

解釋到這里新人又有了疑問(wèn):如果只是禁止使用==,那么_ func()的效果不是一樣的嗎,為什么還要費(fèi)事再套一層數(shù)組呢?

新人的洞察力真的很敏銳,如果只是禁止自動(dòng)生成比較操作的代碼,直接使用函數(shù)類(lèi)型或者切片和map效果是一樣的。但是我們忘了一件事:下劃線字段雖然無(wú)法訪問(wèn)但仍然會(huì)占用實(shí)際的內(nèi)存空間,也就是說(shuō)如果我們用函數(shù)、切片,那么結(jié)構(gòu)體就會(huì)多占用一個(gè)函數(shù)/切片的內(nèi)存。

我們可以算一下,以官方的編譯器為準(zhǔn),在64位操作系統(tǒng)上指針和int都是8字節(jié)大小,一個(gè)函數(shù)的大小大概是8字節(jié),一個(gè)切片目前是24字節(jié),原始結(jié)構(gòu)體A大小是16字節(jié),如果使用_ func(),則大小變成24字節(jié),膨脹50%,如果我們使用_ []int,則大小變成40字節(jié),膨脹了150%!另外添加了新的有實(shí)際大小的字段,還會(huì)影響整個(gè)結(jié)構(gòu)體的內(nèi)存對(duì)齊,導(dǎo)致浪費(fèi)內(nèi)存或者在有特殊要求的接口中出錯(cuò)。

這時(shí)候_ [0]func()便派上用場(chǎng)了,go規(guī)定大小為0的數(shù)組不占用內(nèi)存空間,但字段依舊實(shí)際存在,編譯器也會(huì)照常進(jìn)行類(lèi)型檢查。所以我們既不用浪費(fèi)內(nèi)存空間和改變內(nèi)存對(duì)齊,又可以禁止編譯器生成結(jié)構(gòu)體的比較操作。

至此新人的疑問(wèn)解答完畢,下劃線字段的第一個(gè)實(shí)際應(yīng)用也介紹完了。

阻止結(jié)構(gòu)體被拷貝

首先要聲明,僅靠下劃線字段是不能阻止結(jié)構(gòu)體被拷貝的,我們只能做到讓代碼在幾乎所有代碼檢查工具和IDE里爆出警告信息。

這也是下劃線字段的常見(jiàn)應(yīng)用,在標(biāo)準(zhǔn)庫(kù)里就有,比如sync.Once

// A Once must not be copied after first use.
//
// In the terminology of [the Go memory model],
// the return from f “synchronizes before”
// the return from any call of once.Do(f).
//
// [the Go memory model]: https://go.dev/ref/mem
type Once struct {
	_ noCopy

	// done indicates whether the action has been performed.
	// It is first in the struct because it is used in the hot path.
	// The hot path is inlined at every call site.
	// Placing done first allows more compact instructions on some architectures (amd64/386),
	// and fewer instructions (to calculate offset) on other architectures.
	done atomic.Bool
	m    Mutex
}

其中noCopy長(zhǎng)這樣:

// noCopy may be added to structs which must not be copied
// after the first use.
//
// See https://golang.org/issues/8005#issuecomment-190753527
// for details.
//
// Note that it must not be embedded, due to the Lock and Unlock methods.
type noCopy struct{}

// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock()   {}
func (*noCopy) Unlock() {}

noCopy實(shí)現(xiàn)了sync.Locker,所有實(shí)現(xiàn)了這個(gè)接口的類(lèi)型理論上都不可以被復(fù)制,所有的代碼檢查工具包括自帶的go vet都會(huì)在看到實(shí)現(xiàn)了sync.Locker的類(lèi)型被拷貝時(shí)發(fā)出警告。

而且noCopy的底層類(lèi)型是空結(jié)構(gòu)體,不會(huì)占用內(nèi)存,因此這種用法也不需要我們支付額外的運(yùn)行時(shí)代價(jià)。

美中不足的是這只能產(chǎn)生一些警告,對(duì)這些結(jié)構(gòu)體進(jìn)行拷貝的代碼還是能正常編譯的。

強(qiáng)制指定初始化方式

在golang中用字面量初始化結(jié)構(gòu)體有方式:

type A struct {
    B int64
    C uint64
    D string
}

a := A{1, 2, "3"}
b := A{
    B: 1,
    C: 2,
    D: "3",
}

一個(gè)是在初始化時(shí)不指定字段的名稱(chēng),我們叫匿名初始化,在這種方式下所有字段的值都需要給出,且順序從左到右要和字段定義的順序一致。

第二個(gè)是在初始化時(shí)明確給出字段的名字,我們叫它具名初始化。具名初始化時(shí)不需要給出所有字段的值,未給出的會(huì)用零值進(jìn)行初始化;字段的順序也可以和定義時(shí)的順序不同(不過(guò)有的IDE會(huì)給出警告)。其中a := A{}算是一種特殊的具名初始化——沒(méi)給出字段名,所有全部的字段都用零值初始化。

如果結(jié)構(gòu)體里字段很多,而這些字段中的大多數(shù)又可以使用默認(rèn)的零值,那么具名初始化是一種安全又方便的做法。

匿名初始化則不僅繁瑣,而且因?yàn)橐蕾?lài)字段之間的相對(duì)順序,很容易造成錯(cuò)誤或者因?yàn)樵鰟h字段導(dǎo)致代碼出錯(cuò)。因此一些項(xiàng)目里禁止了這種初始化。然而go并沒(méi)有在編譯器里提供這種禁止機(jī)制,所以我們又只能用下劃線字段模擬了。

我們可以反向利用匿名初始化需要給出每一個(gè)字段的值的特點(diǎn)來(lái)阻止匿名初始化。看個(gè)例子:

// package a
package a

type A struct {
    _ struct{}
    B int64
    C uint64
    D string
}

// package main
func main() {
    obj := a.A{1, 2, "3"} // 編譯報(bào)錯(cuò)
    fmt.Println(obj)
}

編譯代碼會(huì)得到類(lèi)似implicit assignment to unexported field _ in struct literal of type a.A的報(bào)錯(cuò)。

那如果我們偷看了源代碼,發(fā)現(xiàn)A的第一個(gè)字段就是一個(gè)空結(jié)構(gòu)體,然后把代碼改成下面的會(huì)怎么樣:

func main() {
-   obj := a.A{1, 2, "3"} // 編譯報(bào)錯(cuò)
+   obj := a.A{struct{}{}, 1, 2, "3"} // ?
    fmt.Println(obj)
}

答案依然是編譯報(bào)錯(cuò):implicit assignment to unexported field _ in struct literal of type a.A。

還記得我們?cè)陂_(kāi)頭就說(shuō)過(guò)的嗎,下劃線字段不可訪問(wèn),這個(gè)訪問(wèn)包含“初始化”,不可訪問(wèn)意味著沒(méi)法給它初始值,這導(dǎo)致了匿名初始化無(wú)法進(jìn)行。所以偷看答案也沒(méi)有用,我們得老老實(shí)實(shí)對(duì)A使用具名初始化。

同樣因?yàn)槭怯玫目战Y(jié)構(gòu)體,我們不用付出運(yùn)行時(shí)代價(jià)。不過(guò)我推薦還是給出一個(gè)初始化函數(shù)如NewA比較好。

防止錯(cuò)誤的類(lèi)型轉(zhuǎn)換

這個(gè)應(yīng)用我在以前的博客golang的類(lèi)型轉(zhuǎn)換中詳細(xì)介紹過(guò)。

簡(jiǎn)單的說(shuō)golang只要兩個(gè)類(lèi)型的底層類(lèi)型相同,那么就運(yùn)行兩個(gè)類(lèi)型的值之間互相轉(zhuǎn)換。這會(huì)給泛型類(lèi)型帶來(lái)問(wèn)題:

// A Pointer is an atomic pointer of type *T. The zero value is a nil *T.
type Pointer[T any] struct {
    _ noCopy
    v unsafe.Pointer
}

最早的atomic.Pointer長(zhǎng)這樣,它可以原子操作各種類(lèi)型的指針。原子操作只需要地址值并不需要具體的類(lèi)型,因此用unsafe.Pointer是合理的也是最便利的。

但基于golang的類(lèi)型轉(zhuǎn)換規(guī)則,atomic.Pointer[byte]可以和atomic.Pointer[map[int]string]互相轉(zhuǎn)換,因?yàn)樗鼈兂祟?lèi)型參數(shù)不同,底層類(lèi)型是完全相同的。這當(dāng)然很荒謬,因?yàn)閎yte好map別說(shuō)內(nèi)存布局完全不一樣,它們的實(shí)際大小也不同,相互轉(zhuǎn)換不僅沒(méi)有意義還會(huì)造成安全問(wèn)題。

我們需要讓泛型類(lèi)型的底層類(lèi)型不同,那么就需要把類(lèi)型參數(shù)加入字段里;而我們又不想這一補(bǔ)救措施產(chǎn)生運(yùn)行時(shí)開(kāi)銷(xiāo)和影響使用。這時(shí)候就需要下劃線字段救場(chǎng)了:

// A Pointer is an atomic pointer of type *T. The zero value is a nil *T.
type Pointer[T any] struct {
+   // Mention *T in a field to disallow conversion between Pointer types.
+   // See go.dev/issue/56603 for more details.
+   // Use *T, not T, to avoid spurious recursive type definition errors.
+   _ [0]*T

    _ noCopy
    v unsafe.Pointer
}

通過(guò)添加_ [0]*T,我們?cè)谧侄卫锸褂昧祟?lèi)型參數(shù),現(xiàn)在atomic.Pointer[byte]會(huì)有一個(gè)_ [0]*byte字段,atomic.Pointer[map[int]string]會(huì)有一個(gè)_ [0]*map[int]string字段,兩者類(lèi)型完全不同,所以泛型類(lèi)型之間也不再可以互相轉(zhuǎn)換了。

至于零長(zhǎng)度數(shù)組,我們前面已經(jīng)介紹過(guò)了,它和空結(jié)構(gòu)體一樣不會(huì)產(chǎn)生實(shí)際的運(yùn)行開(kāi)銷(xiāo)。

這個(gè)應(yīng)用其實(shí)不是很常見(jiàn),但隨著泛型代碼越來(lái)越常用,我想大多數(shù)人早晚有一天會(huì)見(jiàn)到類(lèi)似代碼的。

緩存行對(duì)齊

我們之前提到,下劃線字段不可訪問(wèn),但仍然實(shí)際占用內(nèi)存空間。所以之前的應(yīng)用都給下劃線字段一些大小為0的類(lèi)型以避免產(chǎn)生開(kāi)銷(xiāo)。

但下面要介紹的這種應(yīng)用反其道而行之,它需要占用空間的特性來(lái)實(shí)現(xiàn)緩存行對(duì)齊。

想象一下你有兩個(gè)原子變量,線程1會(huì)操作變量A,線程2操作變量B:

type Obj struct {
    A atomic.Int64
    B atomic.Int64
}

現(xiàn)代的x86 cpu上一個(gè)緩存行有64字節(jié)(Apple的一些芯片上甚至是128字節(jié)),所以一個(gè)Obj的對(duì)象多半會(huì)存儲(chǔ)在同一個(gè)緩存行里。線程1和線程2看似安全得操作這個(gè)兩個(gè)不同的原子變量,但在運(yùn)行時(shí)看來(lái)兩個(gè)線程會(huì)互相修改同一個(gè)緩存行里的內(nèi)容,這是典型的false sharing,會(huì)造成可觀的性能損失。

我這里不想對(duì)偽共享做過(guò)多的解釋?zhuān)F(xiàn)在你只要知道想避免它,就得讓AB存儲(chǔ)在不同的緩存行里。最典型的就是在AB之間加上其他數(shù)據(jù)做填充,這些數(shù)據(jù)的大小要只是有一個(gè)緩存行也就是64字節(jié)那么大。

我們需要數(shù)據(jù)填充,但又不想填充的數(shù)據(jù)被訪問(wèn)到,那肯定只能選擇下劃線字段了。以runtime里的代碼為例:

type traceMap struct {
    root atomic.UnsafePointer // *traceMapNode (can't use generics because it's notinheap)
    _    cpu.CacheLinePad
    seq  atomic.Uint64
    _    cpu.CacheLinePad
    mem  traceRegionAlloc
}

三個(gè)字段都用_ cpu.CacheLinePad分隔開(kāi)了。而cpu.CacheLinePad的大小是正好一個(gè)緩存行,在arm上它的定義是:

type CacheLinePad struct{ _ [CacheLinePadSize]byte }

// mac arm64
const CacheLinePadSize = 128

CacheLinePad也使用下劃線字段,并且用一個(gè)byte數(shù)組占足了長(zhǎng)度。

我們可以利用類(lèi)似的方法來(lái)保證字段之間按緩存行對(duì)齊。

注意下劃線字段的位置

最后一點(diǎn)不是應(yīng)用場(chǎng)景,而是注意事項(xiàng)。

可以看到,如果我們不想下劃線字段占用內(nèi)存的時(shí)候,這個(gè)字段通常都是結(jié)構(gòu)體的第一個(gè)字段。

這當(dāng)然有可讀性更好的因素在,但還有一個(gè)更重要的影響:

type A struct {
    _    [0]func()
    Name *string
    Age  int
}

type B struct {
    Name *string
    Age  int
    _    [0]func()
}

func main() {
    fmt.Println(unsafe.Sizeof(A{})) // 16字節(jié)
    fmt.Println(unsafe.Sizeof(B{})) // 24字節(jié)
}

是的,字段一樣,對(duì)齊規(guī)則一樣,但B會(huì)多出8字節(jié)。

這是因?yàn)間olang對(duì)結(jié)構(gòu)體的內(nèi)存布局有規(guī)定,結(jié)構(gòu)體里的字段可以有重疊,但這個(gè)重疊不能超過(guò)這個(gè)結(jié)構(gòu)體本身的內(nèi)存范圍。

舉個(gè)例子:

type B struct {
    A *string
    C int
    D struct{}
}

array := [2]B{}

我們有一個(gè)數(shù)組存了兩個(gè)類(lèi)型B的元素,字段D的大小理論上為0,所以如果我們用&array[0].D取D的地址,那么理論上有兩種情況:

  • D和C共享地址,因?yàn)榍懊嬲f(shuō)過(guò)結(jié)構(gòu)體內(nèi)部字段之間發(fā)生重疊是允許的,但在這里這個(gè)方案不行,因?yàn)樽侄沃g還有offset的規(guī)定,字段的offset必須大于等于前面所有字段和內(nèi)存對(duì)齊留下的空洞的大小之和(換句話(huà)說(shuō),也就是當(dāng)前字段的地址到結(jié)構(gòu)體內(nèi)存開(kāi)始地址的距離),如果C和D共享地址,那么D的offset就錯(cuò)了,正確的應(yīng)該是16(D前面有8字節(jié)的A和8字節(jié)的C)而共享地址后會(huì)變成8。offset對(duì)反射和編譯器生成代碼有很重要的影響,所以容不得錯(cuò)誤。
  • 數(shù)組的內(nèi)存是連續(xù)的,所以D和array[1]共享地址,這是不引入填充時(shí)的第二個(gè)選擇,然而這會(huì)導(dǎo)致array[0]的字段可以訪問(wèn)到array[1]的內(nèi)存,往嚴(yán)重說(shuō)這是一種內(nèi)存破壞,只不過(guò)恰好我們的字段大小為0沒(méi)法進(jìn)行有效讀寫(xiě)罷了。而且你考慮過(guò)array[1]的字段D的地址上應(yīng)該放啥了嗎,按照目前的想法是沒(méi)法處理的。

所以go選擇了一種折中的辦法,如果末尾的字段大小為0,則會(huì)在結(jié)構(gòu)體尾部加入一個(gè)內(nèi)存對(duì)齊大小的填充,在我們的結(jié)構(gòu)體里這個(gè)大小是8。這樣offset的計(jì)算不會(huì)出錯(cuò),同時(shí)也不會(huì)訪問(wèn)到不該訪問(wèn)的地址,而D的地址就是填充內(nèi)容起始處的地址。

如果大小為0的字段出現(xiàn)在結(jié)構(gòu)體的開(kāi)頭,上面兩個(gè)問(wèn)題就都不存在了,編譯器自然也不會(huì)再插入不必要的填充物。

所以對(duì)于大小為0的下劃線字段,我們一般放在結(jié)構(gòu)體的開(kāi)頭處,以免產(chǎn)生不必要的開(kāi)銷(xiāo)。

總結(jié)

上面列舉的只是一些最常見(jiàn)的下劃線字段的應(yīng)用,你完全可以因地制宜創(chuàng)造出新的用法。

但別忘了代碼可讀性是第一位的,不要為了炫技而濫用下劃線字段。同時(shí)也要小心不要踩到注意事項(xiàng)里說(shuō)的坑。

到此這篇關(guān)于一文詳解下劃線字段在golang結(jié)構(gòu)體中的應(yīng)用的文章就介紹到這了,更多相關(guān)go下劃線字段內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go標(biāo)準(zhǔn)容器之Ring的使用說(shuō)明

    Go標(biāo)準(zhǔn)容器之Ring的使用說(shuō)明

    這篇文章主要介紹了Go標(biāo)準(zhǔn)容器之Ring的使用說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-05-05
  • Go語(yǔ)言并發(fā)編程 sync.Once

    Go語(yǔ)言并發(fā)編程 sync.Once

    這篇文章要介紹的是Go語(yǔ)言并發(fā)編程 sync.Once,sync.Once用于保證某個(gè)動(dòng)作只被執(zhí)行一次,可用于單例模式中,下面文章我們來(lái)介紹一下它的使用方法,需要的朋友可以參考一下
    2021-10-10
  • Go中init()執(zhí)行順序詳解

    Go中init()執(zhí)行順序詳解

    go允許定義多個(gè)init(),多個(gè)init()會(huì)涉及到執(zhí)行先后的問(wèn)題,本文將詳細(xì)講解Go中init()執(zhí)行順序,感興趣的朋友一起看看吧
    2022-09-09
  • 在goland中讀取tpl文件的圖文操作

    在goland中讀取tpl文件的圖文操作

    這篇文章主要介紹了在goland中讀取tpl文件的圖文操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • Golang并發(fā)編程之調(diào)度器初始化詳解

    Golang并發(fā)編程之調(diào)度器初始化詳解

    這篇文章主要為大家詳細(xì)介紹了Golang并發(fā)編程中關(guān)于調(diào)度器初始化的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2023-03-03
  • 使用Go?goroutine實(shí)現(xiàn)并發(fā)的Clock服務(wù)

    使用Go?goroutine實(shí)現(xiàn)并發(fā)的Clock服務(wù)

    這篇文章主要為大家詳細(xì)介紹了如何使用Go?goroutine實(shí)現(xiàn)并發(fā)的Clock服務(wù),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-06-06
  • golang 中獲取字符串個(gè)數(shù)的方法

    golang 中獲取字符串個(gè)數(shù)的方法

    這篇文章主要介紹了golang 中獲取字符串個(gè)數(shù) ,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-08-08
  • 詳解golang避免循環(huán)import問(wèn)題(“import cycle not allowed”)

    詳解golang避免循環(huán)import問(wèn)題(“import cycle not allowed”)

    這篇文章主要給大家介紹了關(guān)于golang中不允許循環(huán)import問(wèn)題("import cycle not allowed")的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-08-08
  • Golang對(duì)MongoDB數(shù)據(jù)庫(kù)的操作簡(jiǎn)單封裝教程

    Golang對(duì)MongoDB數(shù)據(jù)庫(kù)的操作簡(jiǎn)單封裝教程

    mongodb官方?jīng)]有關(guān)于go的mongodb的驅(qū)動(dòng),因此只能使用第三方驅(qū)動(dòng),mgo就是使用最多的一種。下面這篇文章主要給大家介紹了關(guān)于利用Golang對(duì)MongoDB數(shù)據(jù)庫(kù)的操作簡(jiǎn)單封裝的相關(guān)資料,需要的朋友可以參考下
    2018-07-07
  • golang端口占用檢測(cè)的使用

    golang端口占用檢測(cè)的使用

    這篇文章主要介紹了golang端口占用檢測(cè)的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03

最新評(píng)論

信宜市| 新泰市| 错那县| 凉城县| 醴陵市| 连江县| 彭州市| 林周县| 广东省| 建阳市| 徐州市| 高清| 大同市| 丁青县| 茶陵县| 红桥区| 额敏县| 驻马店市| 青田县| 隆林| 龙川县| 左云县| 郴州市| 贡山| 横峰县| 海晏县| 兴国县| 玛纳斯县| 南平市| 深水埗区| 乌海市| 当阳市| 禄劝| 临猗县| 连云港市| 梁平县| 黔东| 多伦县| 大名县| 长海县| 常熟市|