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

淺談Golang數(shù)據(jù)競(jìng)態(tài)

 更新時(shí)間:2023年02月08日 10:22:39   作者:耳冉鵝  
本文主要介紹了淺談Golang數(shù)據(jù)競(jìng)態(tài),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

本文以一個(gè)簡(jiǎn)單事例的多種解決方案作為引子,用結(jié)構(gòu)體Demo來(lái)總結(jié)各種并發(fā)讀寫的情況

一個(gè)數(shù)據(jù)競(jìng)態(tài)的case

package main

import (
	"fmt"
	"testing"
	"time"
)

func Test(t *testing.T) {
  fmt.Print("getNum(): ")
	for i := 0; i < 10; i++ {
		fmt.Print(strconv.Itoa(getNum()) + " ")
	}
	fmt.Println()

}
func getNum() int {
	var num int
	go func() {
		num = 53
	}()
	time.Sleep(500)
	return num
}

在case中,getNum先聲明一個(gè)變量num,之后在goRoutine中單讀對(duì)num進(jìn)行設(shè)置,而此時(shí)程序也正從函數(shù)中返回num, 因?yàn)椴恢纆oRoutine是否完成了對(duì)num的修改,所以會(huì)導(dǎo)致以下兩種結(jié)果:

  • goRoutine先完成對(duì)num的修改,最后返回5
  • 變量num的值從函數(shù)返回,結(jié)果為默認(rèn)值0

操作完成的順序不同,導(dǎo)致最后的輸出結(jié)果不同,這就是將其稱為數(shù)據(jù)竟態(tài)的原因。

檢查數(shù)據(jù)競(jìng)態(tài)

Go有內(nèi)置的數(shù)據(jù)競(jìng)爭(zhēng)檢測(cè)器,可以使用它來(lái)查看潛在的數(shù)據(jù)競(jìng)爭(zhēng)條件。使用它就像-race在普通的Go命令行工具中添加標(biāo)志一樣。

  • 運(yùn)行時(shí)檢查: go run -race main.go
  • 構(gòu)建時(shí)檢查: go build -race main.go
  • 測(cè)試時(shí)檢查: go test -race main.go

所有避免產(chǎn)生競(jìng)態(tài)背后的核心原則是防止對(duì)同一變量或內(nèi)存位置同時(shí)進(jìn)行讀寫訪問(wèn)

解決方案

1、WaitGroup等待

解決數(shù)據(jù)競(jìng)態(tài)的最直接方法是阻止讀取訪問(wèn)操作直到寫操作完成為止。
可以以最少的麻煩解決問(wèn)題,但必須要保證Add和Done出現(xiàn)次數(shù)一致,否則會(huì)一致阻塞程序,無(wú)限制消耗內(nèi)存,直至資源耗盡服務(wù)宕機(jī)

func getNumByWaitGroup() int {
	var num int
	var wg sync.WaitGroup
	wg.Add(1) // 表示有一個(gè)任務(wù)需要等待,等待任務(wù)數(shù)+1
	go func() {
		num = 53
		wg.Done() // 完成一個(gè)處于等待隊(duì)列的任務(wù),等待任務(wù)-1

		// Done decrements the WaitGroup counter by one.
		// func (wg *WaitGroup) Done() {
		//	wg.Add(-1)
		//}

	}()
	wg.Wait() // 阻塞等待,直到等待隊(duì)列的任務(wù)數(shù)為0
	return num
}

2、Channel阻塞等待

與1相似

func getNumByChannel() int {
	var num int
	ch := make(chan struct{}) // 創(chuàng)建一個(gè)類型為結(jié)構(gòu)體的channel,并初始化為空
	go func() {
		num = 53
		ch <- struct{}{} // 推送一個(gè)空結(jié)構(gòu)體到ch
	}()
	<-ch // 使程序處于阻塞狀態(tài),直到ch獲取到推送的值
	return num
}

3、Channel通道

獲取結(jié)果后通過(guò)通道推送結(jié)果,與前兩種方法不同,該方法不會(huì)進(jìn)行任何阻塞。
相反,保留了阻塞調(diào)用代碼的時(shí)機(jī),因此它允許更高級(jí)別的功能決定自己的阻塞合并發(fā)機(jī)制,而不是將getXX功能視為同步功能

func getNumByChan() <-chan int {
	var num int
	ch := make(chan int) // 創(chuàng)建一個(gè)類型為int的channel
	go func() {
		num = 53
		ch <- num // 推送一個(gè)int到ch
	}()

	return ch // 返回chan
}

4、互斥鎖

上述三種方法解決的是num在寫操作完成后才能讀取的情況
不管讀寫順序如何,只要求它們不能同時(shí)發(fā)生——> 互斥鎖

// 首先,創(chuàng)建一個(gè)結(jié)構(gòu)體,其中包含我們想要返回的值以及一個(gè)互斥實(shí)例
type NumLock struct {
	val int
	m   sync.Mutex
}

func (num *NumLock) Get() int {
	// The `Lock` method of the mutex blocks if it is already locked
	// if not, then it blocks other calls until the `Unlock` method is called
	// Lock方法
	// 調(diào)用結(jié)構(gòu)體對(duì)象的Lock方法將會(huì)鎖定該對(duì)象中的變量;如果沒(méi)有,將會(huì)阻塞其他調(diào)用,直到該互斥對(duì)象的Unlock方法被調(diào)用

	num.m.Lock()
	// 直到該方法返回,該實(shí)例對(duì)象才會(huì)被解鎖
	defer num.m.Unlock()
	// 返回安全類型的實(shí)例對(duì)象中的值
	return num.val
}

func (num *NumLock) Set(val int) {
	// 類似于上面的getNum方法,鎖定num對(duì)象直到寫入“num.val”的值完成
	num.m.Lock()
	defer num.m.Unlock()
	num.val = val
}

func getNumByLock() int {
	// 創(chuàng)建一個(gè)`NumLock`的示例
	num := &NumLock{}
	// 使用“Set”和“Get”來(lái)代替常規(guī)的復(fù)制修改和讀取值,這樣就可以確保只有在寫操作完成時(shí)我們才能進(jìn)行閱讀,反之亦然
	go func() {
		num.Set(53)
	}()
	time.Sleep(500)
	return num.Get()
}

這里要注意,我們無(wú)法保證最后取得的num值
當(dāng)有多個(gè)寫入和讀取操作混合在一起時(shí),使用Mutex互斥可以保證讀寫的值與預(yù)期結(jié)果一致

附上結(jié)果:

完整代碼:

package main

import (
	"fmt"
	"strconv"
	"sync"
	"testing"
	"time"
)

func Test(t *testing.T) {
	fmt.Print("getNum(): ")
	for i := 0; i < 10; i++ {
		fmt.Print(strconv.Itoa(getNum()) + " ")
	}
	fmt.Println()
	fmt.Print("getNumByWaitGroup(): ")
	for i := 0; i < 10; i++ {
		fmt.Print(strconv.Itoa(getNumByWaitGroup()) + " ")
	}
	fmt.Println()
	fmt.Print("getNumByChannel(): ")
	for i := 0; i < 10; i++ {
		fmt.Print(strconv.Itoa(getNumByChannel()) + " ")
	}
	fmt.Println()
	fmt.Print("getNumByChan(): ")
	for i := 0; i < 10; i++ {
		fmt.Print(strconv.Itoa(<-getNumByChan()) + " ")
	}
	fmt.Println()
	fmt.Print("getNumByLock(): ")
	for i := 0; i < 10; i++ {
		fmt.Print(strconv.Itoa(getNumByLock()) + " ")
	}
	fmt.Println()
	fmt.Print("getFact(): ")
	fmt.Println(getFact())
	fmt.Println()
}
func getNum() int {
	var num int
	go func() {
		num = 53
	}()
	time.Sleep(500)
	return num
}

func getNumByWaitGroup() int {
	var num int
	var wg sync.WaitGroup
	wg.Add(1) // 表示有一個(gè)任務(wù)需要等待,等待任務(wù)數(shù)+1
	go func() {
		num = 53
		wg.Done() // 完成一個(gè)處于等待隊(duì)列的任務(wù),等待任務(wù)-1

		// Done decrements the WaitGroup counter by one.
		// func (wg *WaitGroup) Done() {
		//	wg.Add(-1)
		//}

	}()
	wg.Wait() // 阻塞等待,直到等待隊(duì)列的任務(wù)數(shù)為0
	return num
}

func getNumByChannel() int {
	var num int
	ch := make(chan struct{}) // 創(chuàng)建一個(gè)類型為結(jié)構(gòu)體的channel,并初始化為空
	go func() {
		num = 53
		ch <- struct{}{} // 推送一個(gè)空結(jié)構(gòu)體到ch
	}()
	<-ch // 使程序處于阻塞狀態(tài),直到ch獲取到推送的值
	return num
}

func getNumByChan() <-chan int {
	var num int
	ch := make(chan int) // 創(chuàng)建一個(gè)類型為int的channel
	go func() {
		num = 53
		ch <- num // 推送一個(gè)int到ch
	}()

	return ch // 返回chan
}

// 首先,創(chuàng)建一個(gè)結(jié)構(gòu)體,其中包含我們想要返回的值以及一個(gè)互斥實(shí)例
type NumLock struct {
	val int
	m   sync.Mutex
}

func (num *NumLock) Get() int {
	// The `Lock` method of the mutex blocks if it is already locked
	// if not, then it blocks other calls until the `Unlock` method is called
	// Lock方法
	// 調(diào)用結(jié)構(gòu)體對(duì)象的Lock方法將會(huì)鎖定該對(duì)象中的變量;如果沒(méi)有,將會(huì)阻塞其他調(diào)用,直到該互斥對(duì)象的Unlock方法被調(diào)用

	num.m.Lock()
	// 直到該方法返回,該實(shí)例對(duì)象才會(huì)被解鎖
	defer num.m.Unlock()
	// 返回安全類型的實(shí)例對(duì)象中的值
	return num.val
}

func (num *NumLock) Set(val int) {
	// 類似于上面的getNum方法,鎖定num對(duì)象直到寫入“num.val”的值完成
	num.m.Lock()
	defer num.m.Unlock()
	num.val = val
}

func getNumByLock() int {
	// 創(chuàng)建一個(gè)`NumLock`的示例
	num := &NumLock{}
	// 使用“Set”和“Get”來(lái)代替常規(guī)的復(fù)制修改和讀取值,這樣就可以確保只有在寫操作完成時(shí)我們才能進(jìn)行閱讀,反之亦然
	go func() {
		num.Set(53)
	}()
	time.Sleep(500)
	return num.Get()
}

func getFact() []string {
	ch := make(chan string)
	//defer close(ch)
	res := make([]string, 0)
	num := &NumLock{}
	go func() {
		for i := 10; i > 0; i-- {
			num.Set(i)
			ch <- strconv.Itoa(num.Get())
		}
		close(ch)
	}()
	for i := range ch {
		res = append(res, i)
	}
	return res
}

典型數(shù)據(jù)競(jìng)態(tài)

1、循環(huán)計(jì)數(shù)上的競(jìng)態(tài)

func main() {
	var wg sync.WaitGroup
	wg.Add(5)
	for i := 0; i < 5; i++ {
		go func() {
			fmt.Println(i) // Not the 'i' you are looking for.
			wg.Done()
		}()
	}
	wg.Wait()
}

函數(shù)文字中的變量i與循環(huán)使用的變量相同,因此goroutine中的讀取與循環(huán)增量競(jìng)爭(zhēng)。
(此程序通常打印55555,而不是01234)
該程序可以通過(guò)復(fù)制變量來(lái)修復(fù):

func main() {
	var wg sync.WaitGroup
	wg.Add(5)
	for i := 0; i < 5; i++ {
		go func(j int) {
			fmt.Println(j) // Good. Read local copy of the loop counter.
			wg.Done()
		}(i)
	}
	wg.Wait()
}

2、意外共享變量

func ParallelWrite(data []byte) chan error {
	res := make(chan error, 2)
	f1, err := os.Create("file1")
	if err != nil {
		res <- err
	} else {
		go func() {
			// This err is shared with the main goroutine,
			// so the write races with the write below.
			_, err = f1.Write(data)
			res <- err
			f1.Close()
		}()
	}
	f2, err := os.Create("file2") // The second conflicting write to err.
	if err != nil {
		res <- err
	} else {
		go func() {
			_, err = f2.Write(data)
			res <- err
			f2.Close()
		}()
	}
	return res
}

修復(fù)方法是在goroutines中引入新變量(注意使用:=):

	...
	_, err := f1.Write(data)
	...
	_, err := f2.Write(data)
	...

3、無(wú)保護(hù)的全局變量

如果從幾個(gè)goroutine調(diào)用以下代碼,則會(huì)導(dǎo)致service的map產(chǎn)生競(jìng)態(tài)。同一map的并發(fā)讀寫不安全:

var service map[string]net.Addr

func RegisterService(name string, addr net.Addr) {
	service[name] = addr
}

func LookupService(name string) net.Addr {
	return service[name]
}

To make the code safe, protect the accesses with a mutex:

var (
	service   map[string]net.Addr
	serviceMu sync.Mutex
)

func RegisterService(name string, addr net.Addr) {
	serviceMu.Lock()
	defer serviceMu.Unlock()
	service[name] = addr
}

func LookupService(name string) net.Addr {
	serviceMu.Lock()
	defer serviceMu.Unlock()
	return service[name]
}

4、原始無(wú)保護(hù)變量

數(shù)據(jù)競(jìng)態(tài)也可以發(fā)生在原始類型的變量上(bool、int、int64等)

type Watchdog struct{ last int64 }

func (w *Watchdog) KeepAlive() {
	w.last = time.Now().UnixNano() // First conflicting access.
}

func (w *Watchdog) Start() {
	go func() {
		for {
			time.Sleep(time.Second)
			// Second conflicting access.
			if w.last < time.Now().Add(-10*time.Second).UnixNano() {
				fmt.Println("No keepalives for 10 seconds. Dying.")
				os.Exit(1)
			}
		}
	}()
}

即使這種“無(wú)辜”的數(shù)據(jù)競(jìng)爭(zhēng)也可能導(dǎo)致因內(nèi)存訪問(wèn)的非原子性、干擾編譯器優(yōu)化或訪問(wèn)處理器內(nèi)存的重新排序問(wèn)題而導(dǎo)致難以調(diào)試的問(wèn)題。

這場(chǎng)比賽的一個(gè)典型修復(fù)方法是使用通道或互斥體。為了保持無(wú)鎖行為,也可以使用sync/atomic包

type Watchdog struct{ last int64 }

func (w *Watchdog) KeepAlive() {
	atomic.StoreInt64(&w.last, time.Now().UnixNano())
}

func (w *Watchdog) Start() {
	go func() {
		for {
			time.Sleep(time.Second)
			if atomic.LoadInt64(&w.last) < time.Now().Add(-10*time.Second).UnixNano() {
				fmt.Println("No keepalives for 10 seconds. Dying.")
				os.Exit(1)
			}
		}
	}()
}

5、未同步的發(fā)送和關(guān)閉操作

同一通道上的非同步發(fā)送和關(guān)閉操作也可能是一個(gè)競(jìng)態(tài)條件

c := make(chan struct{}) // or buffered channel

// The race detector cannot derive the happens before relation
// for the following send and close operations. These two operations
// are unsynchronized and happen concurrently.
go func() { c <- struct{}{} }()
close(c)

根據(jù)Go內(nèi)存模型,通道上的發(fā)送發(fā)生在該通道的相應(yīng)接收完成之前。要同步發(fā)送和關(guān)閉操作,請(qǐng)使用接收操作來(lái)保證發(fā)送在關(guān)閉前完成:

c := make(chan struct{}) // or buffered channel

go func() { c <- struct{}{} }()
<-c
close(c)

到此這篇關(guān)于淺談Golang數(shù)據(jù)競(jìng)態(tài)的文章就介紹到這了,更多相關(guān)Golang數(shù)據(jù)競(jìng)態(tài)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語(yǔ)言如何獲取goroutine的id

    Go語(yǔ)言如何獲取goroutine的id

    在Go語(yǔ)言中,獲取?goroutine的id并不像其他編程語(yǔ)言那樣容易,但依然有辦法,這篇文章就來(lái)和大家聊聊具體實(shí)現(xiàn)的方法,感興趣的小伙伴可以了解下
    2024-12-12
  • go實(shí)現(xiàn)redigo的簡(jiǎn)單操作

    go實(shí)現(xiàn)redigo的簡(jiǎn)單操作

    golang操作redis主要有兩個(gè)庫(kù),go-redis和redigo,今天我們就一起來(lái)介紹一下redigo的實(shí)現(xiàn)方法,需要的朋友可以參考下
    2018-07-07
  • golang高并發(fā)的深入理解

    golang高并發(fā)的深入理解

    golang從語(yǔ)言級(jí)別上對(duì)并發(fā)提供了支持,而且在啟動(dòng)并發(fā)的方式上直接添加了語(yǔ)言級(jí)的關(guān)鍵字。下面這篇文章主要給大家介紹了關(guān)于golang高并發(fā)的相關(guān)資料,需要的朋友可以參考下
    2019-03-03
  • Golang timer可能造成的內(nèi)存泄漏問(wèn)題分析

    Golang timer可能造成的內(nèi)存泄漏問(wèn)題分析

    本文探討了Golang中timer可能造成的內(nèi)存泄漏問(wèn)題,通過(guò)分析一段代碼,解釋了為什么協(xié)程在調(diào)用timer.Stop()后無(wú)法正常退出,文章指出,timer.Stop()并不關(guān)閉Channel,導(dǎo)致協(xié)程無(wú)法繼續(xù)執(zhí)行,最后,提出了一種修復(fù)方法,并呼吁大家關(guān)注和分享
    2024-12-12
  • Golang自動(dòng)追蹤GitHub上熱門AI項(xiàng)目

    Golang自動(dòng)追蹤GitHub上熱門AI項(xiàng)目

    這篇文章主要為大家介紹了Golang自動(dòng)追蹤GitHub上熱門AI項(xiàng)目,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Go實(shí)現(xiàn)比較時(shí)間大小

    Go實(shí)現(xiàn)比較時(shí)間大小

    這篇文章主要介紹了Go實(shí)現(xiàn)比較時(shí)間大小的方法和示例,非常的簡(jiǎn)單實(shí)用,有需要的小伙伴可以參考下。
    2015-04-04
  • 詳解go-zero如何實(shí)現(xiàn)令牌桶限流

    詳解go-zero如何實(shí)現(xiàn)令牌桶限流

    令牌桶算法既能夠?qū)⑺械恼?qǐng)求平均分布到時(shí)間區(qū)間內(nèi),又能接受服務(wù)器能夠承受范圍內(nèi)的突發(fā)請(qǐng)求,因此是目前使用較為廣泛的一種限流算法,本文就來(lái)看看go-zero如何實(shí)現(xiàn)令牌桶限流的吧
    2023-08-08
  • Golang算法問(wèn)題之?dāng)?shù)組按指定規(guī)則排序的方法分析

    Golang算法問(wèn)題之?dāng)?shù)組按指定規(guī)則排序的方法分析

    這篇文章主要介紹了Golang算法問(wèn)題之?dāng)?shù)組按指定規(guī)則排序的方法,結(jié)合實(shí)例形式分析了Go語(yǔ)言數(shù)組排序相關(guān)算法原理與操作技巧,需要的朋友可以參考下
    2017-02-02
  • Golang異常處理之defer,panic,recover的使用詳解

    Golang異常處理之defer,panic,recover的使用詳解

    這篇文章主要為大家介紹了Go語(yǔ)言異常處理機(jī)制中defer、panic和recover三者的使用方法,文中示例代碼講解詳細(xì),需要的朋友可以參考下
    2022-05-05
  • Go 互斥鎖和讀寫互斥鎖的實(shí)現(xiàn)

    Go 互斥鎖和讀寫互斥鎖的實(shí)現(xiàn)

    本文主要介紹了Go 互斥鎖和讀寫互斥鎖的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11

最新評(píng)論

丰县| 浦东新区| 长兴县| 子长县| 都兰县| 陕西省| 深圳市| 大悟县| 威远县| 甘肃省| 石林| 密云县| 青河县| 常熟市| 洛南县| 临潭县| 广宁县| 二连浩特市| 甘德县| 图们市| 遂平县| 霍城县| 金湖县| 寿宁县| 莲花县| 平泉县| 安阳市| 白河县| 白银市| 通渭县| 米林县| 乐至县| 新营市| 闽侯县| 罗源县| 延寿县| 阳谷县| 凤冈县| 临泽县| 田东县| 石屏县|