可以做哪些網(wǎng)站我想自己建立一個(gè)網(wǎng)站
??個(gè)人博客:Pandaconda-CSDN博客
📣專欄地址:http://t.csdnimg.cn/UWz06
📚專欄簡(jiǎn)介:在這個(gè)專欄中,我將會(huì)分享 Golang 面試中常見的面試題給大家~
??如果有收獲的話,歡迎點(diǎn)贊👍收藏📁,您的支持就是我創(chuàng)作的最大動(dòng)力💪
16. 什么是 sync.Once?
sync.Once
是 Go 語(yǔ)言中的一個(gè)同步原語(yǔ),用于實(shí)現(xiàn)只執(zhí)行一次的操作。它可以保證在多個(gè) Goroutine 中只執(zhí)行一次指定的操作,即使這個(gè)操作被多次調(diào)用。
sync.Once
的使用非常簡(jiǎn)單,只需要?jiǎng)?chuàng)建一個(gè) sync.Once
類型的變量,然后使用 Do()
方法來指定要執(zhí)行的操作。Do()
方法會(huì)保證指定的操作只會(huì)被執(zhí)行一次,無論它被調(diào)用多少次。下面是一個(gè)簡(jiǎn)單的例子:
var once sync.Once
func setup() {fmt.Println("Performing setup...")
}
func main() {// 在第一次調(diào)用時(shí)執(zhí)行 setup 函數(shù)once.Do(setup)// 在第二次調(diào)用時(shí)不執(zhí)行任何操作once.Do(func() { fmt.Println("This shouldn't be printed.") })
}
在這個(gè)例子中,我們首先定義了一個(gè) sync.Once
類型的變量 once
。然后,我們使用 once.Do()
方法來指定要執(zhí)行的操作。在第一次調(diào)用時(shí),Do()
方法會(huì)執(zhí)行 setup()
函數(shù),輸出 "Performing setup..."。在第二次調(diào)用時(shí),Do()
方法不會(huì)執(zhí)行任何操作,因?yàn)?setup()
函數(shù)已經(jīng)被執(zhí)行過了。
需要注意的是,
Do()
方法是阻塞的,也就是說,在第一次調(diào)用還沒有完成之前,后續(xù)的調(diào)用會(huì)被阻塞。這個(gè)特性可以用來保證只有一個(gè) Goroutine 執(zhí)行指定的操作,而其他 Goroutine 等待它完成之后再繼續(xù)執(zhí)行。此外,Do()
方法只會(huì)執(zhí)行一次指定的操作,即使在多個(gè) Goroutine 中調(diào)用它。這個(gè)特性可以用來避免重復(fù)初始化等問題。
17. 什么操作叫做原子操作?
在并發(fā)編程中,原子操作是一種不可中斷的操作,要么全部完成,要么全部不完成。這意味著在多線程環(huán)境下,原子操作可以保證數(shù)據(jù)的一致性和可靠性,防止多個(gè)線程同時(shí)對(duì)同一數(shù)據(jù)進(jìn)行操作而導(dǎo)致的競(jìng)爭(zhēng)條件和數(shù)據(jù)不一致。
在 Go 語(yǔ)言中,sync/atomic
包提供了一些原子操作函數(shù),用于在多線程環(huán)境中執(zhí)行原子操作。這些原子操作函數(shù)可以確保對(duì)共享變量的訪問是原子的,即不會(huì)被其他線程打斷。
例如,atomic.AddInt64()
函數(shù)可以對(duì)一個(gè) int64
類型的變量進(jìn)行原子加法操作。以下是一個(gè)簡(jiǎn)單的示例:
package main
import ("fmt""sync/atomic"
)
func main() {var count int64// 對(duì) count 變量進(jìn)行 100 次原子加法操作for i := 0; i < 100; i++ {atomic.AddInt64(&count, 1)}fmt.Println("count:", count)
}
在這個(gè)例子中,我們首先定義了一個(gè) int64
類型的變量 count
,并使用 atomic.AddInt64()
函數(shù)對(duì)它進(jìn)行 100 次原子加法操作。AddInt64()
函數(shù)的第一個(gè)參數(shù)是一個(gè)指向 int64
類型變量的指針,它告訴函數(shù)要對(duì)哪個(gè)變量進(jìn)行原子操作。第二個(gè)參數(shù)是要添加的值。在這個(gè)例子中,我們每次添加的值都是 1。最后,我們輸出 count
變量的值,應(yīng)該是 100。
需要注意的是,原子操作函數(shù)僅保證對(duì)共享變量的訪問是原子的,但并不能保證對(duì)多個(gè)變量之間的操作是原子的。如果需要對(duì)多個(gè)變量進(jìn)行原子操作,可以使用互斥鎖或其他同步機(jī)制來保證線程安全。
18. Go 原子操作有哪些?
Go atomic 包是最輕量級(jí)的鎖(也稱無鎖結(jié)構(gòu)),可以在不形成臨界區(qū)和創(chuàng)建互斥量的情況下完成并發(fā)安全的值替換操作,不過這個(gè)包只支持 int32/int64/uint32/uint64/uintptr 這幾種數(shù)據(jù)類型的一些基礎(chǔ)操作(增減、交換、載入、存儲(chǔ)等)。
概念:
原子操作僅會(huì)由一個(gè)獨(dú)立的 CPU 指令代表和完成。原子操作是無鎖的,常常直接通過 CPU 指令直接實(shí)現(xiàn)。 事實(shí)上,其它同步技術(shù)的實(shí)現(xiàn)常常依賴于原子操作。
使用場(chǎng)景:
當(dāng)我們想要對(duì)某個(gè)變量并發(fā)安全的修改,除了使用官方提供的 mutex
,還可以使用 sync/atomic 包的原子操作,它能夠保證對(duì)變量的讀取或修改期間不被其他的協(xié)程所影響。
atomic 包提供的原子操作能夠確保任一時(shí)刻只有一個(gè) goroutine 對(duì)變量進(jìn)行操作,善用 atomic 能夠避免程序中出現(xiàn)大量的鎖操作。
常見操作:
-
增減 Add
-
載入 Load
-
比較并交換 CompareAndSwap
-
交換 Swap
-
存儲(chǔ) Store
atomic 操作的對(duì)象是一個(gè)地址,你需要把可尋址的變量的地址作為參數(shù)傳遞給方法,而不是把變量的值傳遞給方法。下面將分別介紹這些操作:
增減操作
此類操作的前綴為 Add
:
func AddInt32(addr *int32, delta int32) (new int32)
func AddInt64(addr *int64, delta int64) (new int64)
func AddUint32(addr *uint32, delta uint32) (new uint32)
func AddUint64(addr *uint64, delta uint64) (new uint64)
func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)
需要注意的是,第一個(gè)參數(shù)必須是指針類型的值,通過指針變量可以獲取被操作數(shù)在內(nèi)存中的地址,從而施加特殊的 CPU 指令,確保同一時(shí)間只有一個(gè) goroutine 能夠進(jìn)行操作。
使用舉例:
func add(addr *int64, delta int64) {atomic.AddInt64(addr, delta) //加操作fmt.Println("add opts: ", *addr)
}
載入操作
此類操作的前綴為 Load
:
func LoadInt32(addr *int32) (val int32)
func LoadInt64(addr *int64) (val int64)
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
func LoadUint32(addr *uint32) (val uint32)
func LoadUint64(addr *uint64) (val uint64)
func LoadUintptr(addr *uintptr) (val uintptr)
// 特殊類型: Value類型,常用于配置變更
func (v *Value) Load() (x interface{}) {}
載入操作能夠保證原子的讀變量的值,當(dāng)讀取的時(shí)候,任何其他 CPU 操作都無法對(duì)該變量進(jìn)行讀寫,其實(shí)現(xiàn)機(jī)制受到底層硬件的支持。
使用示例:
func load(addr *int64) {fmt.Println("load opts: ", atomic.LoadInt64(&opts))
}
比較并交換
此類操作的前綴為 CompareAndSwap
,該操作簡(jiǎn)稱 CAS,可以用來實(shí)現(xiàn)樂觀鎖:
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)
func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)
func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)
該操作在進(jìn)行交換前首先確保變量的值未被更改,即仍然保持參數(shù) old
所記錄的值,滿足此前提下才進(jìn)行交換操作。CAS 的做法類似操作數(shù)據(jù)庫(kù)時(shí)常見的樂觀鎖機(jī)制。
需要注意的是,當(dāng)有大量的 goroutine 對(duì)變量進(jìn)行讀寫操作時(shí),可能導(dǎo)致 CAS 操作無法成功,這時(shí)可以利用 for 循環(huán)多次嘗試。
使用示例:
func compareAndSwap(addr *int64, oldValue int64, newValue int64) {if atomic.CompareAndSwapInt64(addr, oldValue, newValue) {fmt.Println("cas opts: ", *addr)return}
}
交換
此類操作的前綴為 Swap
:
func SwapInt32(addr *int32, new int32) (old int32)
func SwapInt64(addr *int64, new int64) (old int64)
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)
func SwapUint32(addr *uint32, new uint32) (old uint32)
func SwapUint64(addr *uint64, new uint64) (old uint64)
func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)
相對(duì)于 CAS,明顯此類操作更為暴力直接,并不管變量的舊值是否被改變,直接賦予新值然后返回被替換的值。
func swap(addr *int64, newValue int64) {atomic.SwapInt64(addr, newValue)fmt.Println("swap opts: ", *addr)
}
存儲(chǔ)
此類操作的前綴為 Store
:
func StoreInt32(addr *int32, val int32)
func StoreInt64(addr *int64, val int64)
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
func StoreUint32(addr *uint32, val uint32)
func StoreUint64(addr *uint64, val uint64)
func StoreUintptr(addr *uintptr, val uintptr)
// 特殊類型: Value類型,常用于配置變更
func (v *Value) Store(x interface{})
此類操作確保了寫變量的原子性,避免其他操作讀到了修改變量過程中的臟數(shù)據(jù)。
func store(addr *int64, newValue int64) {atomic.StoreInt64(addr, newValue)fmt.Println("store opts: ", *addr)
}