重慶分公司,新征程啟航
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊(cè)、服務(wù)器等服務(wù)
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊(cè)、服務(wù)器等服務(wù)
今天小編給大家分享一下go語(yǔ)言同步機(jī)制是什么及怎么實(shí)現(xiàn)的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。
讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來(lái)自于我們對(duì)這個(gè)行業(yè)的熱愛(ài)。我們立志把好的技術(shù)通過(guò)有效、簡(jiǎn)單的方式提供給客戶,將通過(guò)不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名注冊(cè)、網(wǎng)站空間、營(yíng)銷軟件、網(wǎng)站建設(shè)、婺城網(wǎng)站維護(hù)、網(wǎng)站推廣。
go同步機(jī)制有:1、channel,著重并發(fā)問(wèn)題中的數(shù)據(jù)流動(dòng),把流動(dòng)的數(shù)據(jù)放到channel中,就能使用channel解決這個(gè)并發(fā);2、Sync.Mutex,擁有Lock、Unlock兩個(gè)方法,主要實(shí)現(xiàn)思想體現(xiàn)在Lock函數(shù)中;3、Sync.waitGroup;4、Sync.Once;5、Sync.context;6、Sync.pool;7、atomic包,針對(duì)變量進(jìn)行操作。
Golang的提供的同步機(jī)制有sync模塊下的Mutex、WaitGroup以及語(yǔ)言自身提供的chan等。
概述
Golang以如此明顯的方式告訴我們:。
優(yōu)點(diǎn):channel的核心是數(shù)據(jù)流動(dòng),關(guān)注到并發(fā)問(wèn)題中的數(shù)據(jù)流動(dòng),把流動(dòng)的數(shù)據(jù)放到channel中,就能使用channel解決這個(gè)并發(fā)
問(wèn)題,而且使用channel是線程安全的并且不會(huì)有數(shù)據(jù)沖突,比鎖好用多了
缺點(diǎn):不太適應(yīng)同步太復(fù)雜的場(chǎng)景,比如多協(xié)程的同步等待問(wèn)題,而且存在死鎖問(wèn)題
分類
channel類型:無(wú)緩沖和緩沖類型
channel有兩種形式的,一種是無(wú)緩沖的,一個(gè)線程向這個(gè)channel發(fā)送了消息后,會(huì)阻塞當(dāng)前的這個(gè)線程,知道其他線程去接收這個(gè)channel的消息。無(wú)緩沖的形式如下:
intChan := make(chan int)
帶緩沖的channel,是可以指定緩沖的消息數(shù)量,當(dāng)消息數(shù)量小于指定值時(shí),不會(huì)出現(xiàn)阻塞,超過(guò)之后才會(huì)阻塞,需要等待其他線程去接收channel處理,帶緩沖的形式如下:
//3為緩沖數(shù)量
intChan := make(chan int, 3)
舉例
type Person struct {
Name string
Age uint8
Address Addr
}
type Addr struct {
city string
district string
}
/*
測(cè)試channel傳輸復(fù)雜的Struct數(shù)據(jù)
*/
func testTranslateStruct() {
personChan := make(chan Person, 1)
person := Person{"xiaoming", 10, Addr{"shenzhen", "longgang"}}
personChan <- person
person.Address = Addr{"guangzhou", "huadu"}
fmt.Printf("src person : %+v \n", person)
newPerson := <-personChan
fmt.Printf("new person : %+v \n", newPerson)
}
在實(shí)際應(yīng)用過(guò)程中,等待channel 結(jié)束信號(hào)的過(guò)程可能不是無(wú)期限的,一般會(huì)伴隨一個(gè)timer,超時(shí)時(shí)間如下面所示:
/*
檢查channel讀寫(xiě)超時(shí),并做超時(shí)的處理
*/
func testTimeout() {
g := make(chan int)
quit := make(chan bool)
go func() {
for {
select {
case v := <-g:
fmt.Println(v)
case <-time.After(time.Second * time.Duration(3)):
quit <- true
fmt.Println("超時(shí),通知主線程退出")
return
}
}
}()
for i := 0; i < 3; i++ {
g <- i
}
<-quit
fmt.Println("收到退出通知,主線程退出")
}
Mutex擁有Lock、Unlock兩個(gè)方法,主要的實(shí)現(xiàn)思想都體現(xiàn)在Lock函數(shù)中。
Lock執(zhí)行時(shí),分三種情況:
無(wú)沖突 通過(guò)CAS操作把當(dāng)前狀態(tài)設(shè)置為加鎖狀態(tài);
有沖突 開(kāi)始自旋,并等待鎖釋放,如果其他Goroutine在這段時(shí)間內(nèi)釋放了該鎖, 直接獲得該鎖;如果沒(méi)有釋放,進(jìn)入3;
有沖突,且已經(jīng)過(guò)了自旋階段 通過(guò)調(diào)用semacquire函數(shù)來(lái)讓當(dāng)前Goroutine進(jìn)入等待狀態(tài)。
無(wú)沖突時(shí)是最簡(jiǎn)單的情況;有沖突時(shí),首先進(jìn)行自旋,是從效率方面考慮的, 因?yàn)榇蠖鄶?shù)的Mutex保護(hù)的代碼段都很短,經(jīng)過(guò)短暫的自旋就可以獲得;如果自旋等待無(wú)果,就只好通過(guò)信號(hào)量來(lái)讓當(dāng)前 Goroutine進(jìn)入等待了。
Channel在某些同步場(chǎng)景下,使用略顯復(fù)雜,不管是使用多個(gè)channel還是使用channel數(shù)組,如下:
func coordinateWithChan() {
sign := make(chan struct{}, 2)
num := int32(0)
fmt.Printf("The number: %d [with chan struct{}]\n", num)
max := int32(10)
go addNum(&num, 1, max, func() {
sign <- struct{}{}
})
go addNum(&num, 2, max, func() {
sign <- struct{}{}
})
<-sign
<-sign
}
所以Sync.waitGroup 就顯得更為優(yōu)雅,Sync.waitGroup 用來(lái)等待一組goroutines的結(jié)束,在主Goroutine里聲明,并且設(shè)置要等待的goroutine的個(gè)數(shù),每個(gè)goroutine執(zhí)行完成之后調(diào)用 Done,最后在主Goroutines 里Wait即可。類似于JAVA中的CountDownLatch或者循環(huán)屏障,并且Sync.waitGroup可以被重復(fù)使用,提供了如下API:
func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()
但是Sync.waitGroup的使用需要遵循一些規(guī)則,避免拋出Panic:
a. 錯(cuò)誤調(diào)用Done方法, 導(dǎo)致waitGroup內(nèi)部計(jì)數(shù)值出現(xiàn)負(fù)數(shù)的情況
b. 錯(cuò)誤的調(diào)用Add方法,在waitGroup內(nèi)部計(jì)數(shù)值到達(dá)0的時(shí)候,Add方法被調(diào)用,導(dǎo)致應(yīng)該被喚起的goroutine沒(méi)有被喚起,就開(kāi)始了新的一輪計(jì)數(shù)周期
所以在調(diào)用的時(shí)候,就要遵循一下原則:
先統(tǒng)一Add,再并發(fā)Done,最后Wait
Sync.once實(shí)現(xiàn)方式是內(nèi)部包含一個(gè)int32位的標(biāo)志,用來(lái)判斷方式是否被執(zhí)行過(guò),標(biāo)志值更改的時(shí)機(jī)為方法執(zhí)行完之后,當(dāng)有多個(gè)goroutine進(jìn)行調(diào)用的時(shí)候,使用double-check方式進(jìn)行驗(yàn)證,首先在在沒(méi)有同步方式的情況下,進(jìn)行標(biāo)志值的判定,為0則競(jìng)爭(zhēng)獲取mutex鎖,進(jìn)入臨界區(qū)內(nèi),此時(shí)會(huì)在此進(jìn)行標(biāo)志值的判斷,確保方法真的被執(zhí)行一次。double-check第一次是為了更快的進(jìn)行判斷,但是存在錯(cuò)誤的情況,第二次check是為了正確的確定標(biāo)志值此時(shí)的狀態(tài)。
使用:
func main() {
var once sync.Once
onceBody := func() {
time.Sleep(3e9)
fmt.Println("Only once")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
j := i
go func(int) {
once.Do(onceBody)
fmt.Println(j)
done <- true
}(j)
}
//給一部分時(shí)間保證能夠輸出完整【方法一】
//for i := 0; i < 10; i++ {
// <-done
//}
//給一部分時(shí)間保證能夠輸出完整【方法二】
<-done
time.Sleep(3e9)
}
場(chǎng)景
當(dāng)需要進(jìn)行多批次的計(jì)算任務(wù)同步,或者需要一對(duì)多的協(xié)作流程的時(shí)候
使用舉例
func coordinateWithContext() {
total := 12
var num int32
fmt.Printf("The number: %d [with context.Context]\n", num)
cxt, cancelFunc := context.WithCancel(context.Background())
for i := 1; i <= total; i++ {
go addNum(&num, i, func() {
if atomic.LoadInt32(&num) == int32(total) {
cancelFunc()
}
})
}
<-cxt.Done()
fmt.Println("End.")
}
注意事項(xiàng)
a.如何生成自己的context
通過(guò)WithCancel、WithDeadline、WithTimeout和WithValue四個(gè)方法從context.Background中派生出自己的子context
注意context.background這個(gè)上下文根節(jié)點(diǎn)僅僅是一個(gè)最基本的支點(diǎn),它不提供任何額外的功能,也就是說(shuō),它既不可以被撤銷(cancel),也不能攜帶任何數(shù)據(jù),在使用是必須通過(guò)以上4種方法派生出自己的context
b.子context是會(huì)繼承父context的值
c.撤銷消息的傳播
撤銷消息會(huì)按照深度遍歷的方式傳播給子context(注意因?yàn)槎鄏outine調(diào)用的原因,最終的撤銷順序可能不會(huì)是深度遍歷的順序)
,在遍歷的過(guò)程中,通過(guò)WithCancel、WithDeadline、WithTimeout派生的context會(huì)被撤銷,但是通過(guò)WithValue方法派生的context不會(huì)被撤銷
我們調(diào)用sync/atomic中的幾個(gè)函數(shù)可以對(duì)幾種簡(jiǎn)單的類型進(jìn)行原子操作。這些類型包括int32,int64,uint32,uint64,uintptr,unsafe.Pointer,共6個(gè)。這些函數(shù)的原子操作共有5種:增或減,比較并交換、載入、存儲(chǔ)和交換它們提供了不同的功能,切使用的場(chǎng)景也有區(qū)別。
增或減
顧名思義,原子增或減即可實(shí)現(xiàn)對(duì)被操作值的增大或減少。因此該操作只能操作數(shù)值類型。
被用于進(jìn)行增或減的原子操作都是以“Add”為前綴,并后面跟針對(duì)具體類型的名稱。
//方法源碼
func AddUint32(addr *uint32, delta uint32) (new uint32)
增
栗子:(在原來(lái)的基礎(chǔ)上加n)
atomic.AddUint32(&addr,n)
減
栗子:(在原來(lái)的基礎(chǔ)上加n(n為負(fù)數(shù)))
atomic.AddUint32(*addr,uint32(int32(n)))
//或
atomic.AddUint32(&addr,^uint32(-n-1))
比較并交換
比較并交換----Compare And Swap 簡(jiǎn)稱CAS
他是假設(shè)被操作的值未曾被改變(即與舊值相等),并一旦確定這個(gè)假設(shè)的真實(shí)性就立即進(jìn)行值替換
如果想安全的并發(fā)一些類型的值,我們總是應(yīng)該優(yōu)先使用CAS
//方法源碼
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
栗子:(如果addr和old相同,就用new代替addr)
ok:=atomic.CompareAndSwapInt32(&addr,old,new)
載入
如果一個(gè)寫(xiě)操作未完成,有一個(gè)讀操作就已經(jīng)發(fā)生了,這樣讀操作使很糟糕的。
為了原子的讀取某個(gè)值sync/atomic代碼包同樣為我們提供了一系列的函數(shù)。這些函數(shù)都以"Load"為前綴,意為載入。
//方法源碼
func LoadInt32(addr *int32) (val int32)
栗子
fun addValue(delta int32){
for{
v:=atomic.LoadInt32(&addr)
if atomic.CompareAndSwapInt32(&v,addr,(delta+v)){
break;
}
}
}
存儲(chǔ)
與讀操作對(duì)應(yīng)的是寫(xiě)入操作,sync/atomic也提供了與原子的值載入函數(shù)相對(duì)應(yīng)的原子的值存儲(chǔ)函數(shù)。這些函數(shù)的名稱均以“Store”為前綴
在原子的存儲(chǔ)某個(gè)值的過(guò)程中,任何cpu都不會(huì)進(jìn)行針對(duì)進(jìn)行同一個(gè)值的讀或?qū)懖僮鳌H绻覀儼阉嗅槍?duì)此值的寫(xiě)操作都改為原子操作,那么就不會(huì)出現(xiàn)針對(duì)此值的讀操作讀操作因被并發(fā)的進(jìn)行而讀到修改了一半的情況。
原子操作總會(huì)成功,因?yàn)樗槐仃P(guān)心被操作值的舊值是什么。
//方法源碼
func StoreInt32(addr *int32, val int32)
栗子
atomic.StoreInt32(被操作值的指針,新值)
atomic.StoreInt32(&value,newaddr)
交換
原子交換操作,這類函數(shù)的名稱都以“Swap”為前綴。
與CAS不同,交換操作直接賦予新值,不管舊值。
會(huì)返回舊值
//方法源碼
func SwapInt32(addr *int32, new int32) (old int32)
栗子
atomic.SwapInt32(被操作值的指針,新值)(返回舊值)
oldval:=atomic.StoreInt32(&value,newaddr)
1. 什么是Sync包?
Package sync provides basic synchronization primitives such as mutual exclusion locks. Other than the Once and WaitGroup types, most are intended for use by low-level library routines. Higher-level synchronization is better done via channels and communication.
Values containing the types defined in this package should not be copied.
這句話大意是說(shuō):
Sync包同步提供基本的同步原語(yǔ),如互斥鎖。 除了Once和WaitGroup類型之外,大多數(shù)類型都是供低級(jí)庫(kù)例程使用的。 通過(guò)Channel和溝通可以更好地完成更高級(jí)別的同步。并且此包中的值在使用過(guò)后不要拷貝。
從描述中可以看到的是,golang 并不推薦這個(gè)包中的大多數(shù)并發(fā)控制方法,但還是提供了相關(guān)方法,主要原因是golang中提倡以共享內(nèi)存的方式來(lái)通信:
不要以共享內(nèi)存的方式來(lái)通信,作為替代,我們應(yīng)該以通信的手段來(lái)共享內(nèi)存
共享內(nèi)存的方式使得多線程中的通信變得簡(jiǎn)單,但是在并發(fā)的安全性控制上將變得異常繁瑣。
正確性不是我們唯一想要的,我們想要的還有系統(tǒng)的可伸縮性,以及可理解性,我覺(jué)得這點(diǎn)非常重要,比如現(xiàn)在廣泛使用的Raft算法。
2. 包中的Type
包中主要有: Locker, Cond, Map, Mutex, Once, Pool,
RWMutex, WaitGroup
type Locker interface {
Lock()
Unlock()
}
type Cond struct {
// L is held while observing or changing the condition
L Locker
}
3. 什么是鎖,為什么需要鎖?
鎖是sync包中的核心,他主要有兩個(gè)方法,加鎖和解鎖。
在單線程運(yùn)行的時(shí)候程序是順序執(zhí)行的,程序?qū)?shù)據(jù)的訪問(wèn)也是:
讀取=> 一頓操作(加減乘除之類的) => 寫(xiě)回原地址
但是一旦程序中進(jìn)行了并發(fā)編程,也就是說(shuō),某一個(gè)函數(shù)可能同時(shí)被不同的線程執(zhí)行的時(shí)候,以時(shí)間為維度會(huì)發(fā)生以下情況:
可以看到的是,A地址的數(shù)字被執(zhí)行了兩次自增,若A=5,我們?cè)趫?zhí)行完成后預(yù)期的A值是7,但是在這種情況下我們得到的A卻是6,bug了~
還有很多類似的并發(fā)錯(cuò)誤,所以才有鎖的引入。若是我們?cè)诰€程2讀取A的值的時(shí)候?qū)進(jìn)行加鎖,讓線程2等待,線程1執(zhí)行完成之后在執(zhí)行線程2,這樣就能夠保證數(shù)據(jù)的正確性。但是正確性不是我們唯一想要的。
4 寫(xiě)更優(yōu)雅的代碼
在很多語(yǔ)言中我們經(jīng)常為了保證數(shù)據(jù)安全正確,會(huì)在并發(fā)的時(shí)候?qū)?shù)據(jù)加鎖
Lock()
doSomething()
Unlock()
Golang在此包中也提供了相關(guān)的鎖,但是標(biāo)明了"most are intended for use by low-level library routines" 所以我這里只對(duì) Once and WaitGroup types做簡(jiǎn)述。
5.Once 對(duì)象
Once 是一個(gè)可以被多次調(diào)用但是只執(zhí)行一次,若每次調(diào)用Do時(shí)傳入?yún)?shù)f不同,但是只有第一個(gè)才會(huì)被執(zhí)行。
func (o *Once) Do(f func())
var once sync.Once
onceBody := func() {
fmt.Println("Only once")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
once.Do(onceBody)
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
如果你執(zhí)行這段代碼會(huì)發(fā)現(xiàn),雖然調(diào)用了10次,但是只執(zhí)行了1次。BTW:這個(gè)東西可以用來(lái)寫(xiě)單例。
6. WaitGroup
下面是個(gè)官方的例子:
var wg sync.WaitGroup
var urls = []string{
"http://www.golang.org/",
"http://www.google.com/",
"http://www.somestupidname.com/",
}
for _, url := range urls {
// Increment the WaitGroup counter.
wg.Add(1)
// Launch a goroutine to fetch the URL.
go func(url string) {
// Decrement the counter when the goroutine completes.
defer wg.Done()
// Fetch the URL.
http.Get(url)
}(url)
}
// Wait for all HTTP fetches to complete.
wg.Wait()
7. 簡(jiǎn)述
Golang中高級(jí)的并發(fā)可以通過(guò)channel來(lái)實(shí)現(xiàn),這是golang所倡導(dǎo)的,但是go也提供了鎖等先關(guān)操作。
以上就是“go語(yǔ)言同步機(jī)制是什么及怎么實(shí)現(xiàn)”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。