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

GoLang string類型深入分析

 更新時間:2023年01月28日 14:00:28   作者:i-neojos  
string 作為 go 語言中的基礎類型,其實有一些需要反復揣摩的,可能是我們使用的場景太簡單,也可能是我們不需要那可憐的一點優(yōu)化來提高性能,對它也就沒那么上心了

文章運行環(huán)境:go version go1.16.6 darwin/amd64

并發(fā)不安全

看下面的代碼,大家覺得會輸出什么?大多數人應該都會覺得輸出""、abc、neoj 這三種情況,但真實的情況并不是這樣,真實情況是只輸出 “” 空字符串。

結合日常的工作,類似這種并發(fā)操作同一個變量的情況也比較常見,為什么業(yè)務沒有發(fā)生異常問題?

var name string = ""
func main() {
	go func() {
		for {
			name = "abc"
		}
	}()
	go func() {
		for {
			name = "neoj"
		}
	}()
	for {
		fmt.Println(name)
	}
}

1.14 之后引入了 G 搶占式調度,那為什么代碼中的兩個協(xié)程沒有執(zhí)行呢?其實是編譯器做了優(yōu)化,這兩個協(xié)程被省略掉了。

我們對代碼做一點調整,在協(xié)程中加一行空的輸出,輸出結果中出現了一些特例,比如:neo、abca。其中,neo 字符串長度等于 abc 的長度,而 abca 的長度等于 neoj 的長度。

var name string = ""
func main() {
	go func() {
		for {
			name = "abc"
			fmt.Printf("")
		}
	}()
	go func() {
		for {
			name = "neoj"
			fmt.Printf("")
		}
	}()
	for {
		if name != "abc" && name != "neoj" {
			fmt.Println(name)
		}
	}
}

例子說明,string 的賦值并不是原子的。

Go 語言中 string 的內存結果如下,它包含兩部分:Data 表示實際的數據部分,而 Len 表示字符串的長度。

所以,通過方法 len 來計算字符串的長度并不會有性能開銷,len 方法會直接返回結構體的 Len 屬性;而傳遞字符串類型的參數,使用指針類型和值類型,性能上也不會有太大差別。

type StringHeader struct {
	Data uintptr
	Len  int
}

字符串的并發(fā)不安全,主要就是給這兩個字段的賦值,沒有辦法保證原子性。參考 runtime/string.go 中的源碼,我們可以了解字符串生成過程。

并發(fā)賦值的情況下,Data 指向的地址和 Len 無法保證一一對應。所以,通過 Data 獲取到內存的首地址,通過 Len 去讀取指定長度的內存時,就會出現內存讀取異常的情況。

func rawstring(size int) (s string, b []byte) {
	p := mallocgc(uintptr(size), nil, false)
	stringStructOf(&s).str = p
	stringStructOf(&s).len = size
	*(*slice)(unsafe.Pointer(&b)) = slice{p, size, size}
	return
}

rawstring 函數在字符串拼接的時候被調用,我們代碼中創(chuàng)建一個字符串類型,每次都生成一份新的內存空間。特別強調,創(chuàng)建和字符串賦值需要區(qū)分開來。賦值的過程其實是值拷貝,拷貝的便是 StringHeader 結構體。

var name string = ""
func main() {
	blog := name
	fmt.Println(blog)
}

上面的變量 blog 是 name 的值拷貝,底層指向的字符串是同一塊內存空間。這個賦值過程中,發(fā)生拷貝的只是外層的 StringHeader 對象。

Go 中通過 unsafe 包可以強制對內存數據做類型轉換,我們將 blog 和 name 的內存地址打印出來比較一下。最終打印輸出兩個變量的地址和Data地址??梢钥闯?,賦值前后,Data指向的地址并沒有發(fā)生變化。

type StringHeader struct {
	Data uintptr
	Len  int
}
var name string = "g"
func main() {
	blog := name
	n := (*StringHeader)(unsafe.Pointer(&name))
	b := (*StringHeader)(unsafe.Pointer(&blog))
	fmt.Println(&n, n.Data)    // 0xc00018a020 17594869
	fmt.Println(&b, b.Data)    // 0xc00018a028 17594869
}

string 并發(fā)不安全讀寫,會導致線上服務偶發(fā) panic。比如使用 json 對內存異常的 string 做序列化的時候。下面的例子中,其中一個協(xié)程用來賦值為空,非常容易復現 panic。

type People struct {
	Name string
}
var p *People = new(People)
func main() {
	go func() {
		for {
			p.Name = ""
		}
	}()
	go func() {
		for {
			p.Name = "neoj"
		}
	}()
	for {
		_, _ = json.Marshal(p)
	}
}

下面是 panic 的堆棧信息,空字符串的 Data 指向的是 nil 的地址,而并發(fā)導致 Len 字段有值,最終導致發(fā)生 panic。

競態(tài)競爭

對同一個變量并發(fā)讀寫,如果沒有使用輔助的同步操作,就會出現不符合預期的情況。直白的講,我們開發(fā)完一個程序之后,針對同樣的輸入,會輸出什么結果,我們是不確定的。

可以參考 The Go Memory Model 的介紹,強調一下數據競爭的概念:

A data race is defined as a write to a memory location happening concurrently with another read or write to that same location, unless all the accesses involved are atomic data accesses as provided by the sync/atomic package

幸運的是,Go 已經集成了現成的工具來診斷數據競爭:-race。在 go build、或者直接執(zhí)行的時候,指定 -race 屬性,系統(tǒng)會做數據競爭檢測,并打印輸出。

以最近的代碼為例,如果你使用的也是 goland 編譯器,只需要在 Run Configurations / Go tool arguments 中指定 -race 屬性,運行程序,就會出現下面的檢測結果:

面對生產環(huán)境,-race 有比較嚴重的性能開銷,我們最好是開發(fā)環(huán)境做競態(tài)檢測。

-race 是通過編譯器注入代碼來執(zhí)行檢測的,在函數執(zhí)行前、執(zhí)行后都會做內存統(tǒng)計。也就是說:只有被執(zhí)行到的代碼才能被檢測到。所以,如果開發(fā)階段做競態(tài)檢測的話,一定要保證代碼被執(zhí)行到了。

再加上埋點的內存統(tǒng)計也是有策略的,也不可能保證存在數據競爭的代碼就一定會被檢測出來,最好可以多執(zhí)行幾次來避免這種情況。

字符串優(yōu)化

因字符串并發(fā)讀寫導致的 panic,很容易被 Go 的字符串優(yōu)化帶偏。

我在第一次遇到這種情況的時候,想到的居然是:會不會是底層優(yōu)化導致的。因為發(fā)生 panic 的代碼用到了 map 的數據結構。這種想法很快被我用測試用例排除了。

[]byte 到 string 類型轉換是比較常規(guī)的操作,正常情況下,轉換都會申請了一份新的內存空間。但 Go 為了提高性能,在某些場景下 string 和 []byte 會共用一份內存空間,這種場景下也能寫亂內存。

// slicebytetostringtmp returns a "string" referring to the actual []byte bytes.
//
func slicebytetostringtmp(ptr *byte, n int) (str string) {
	if raceenabled && n > 0 {
		racereadrangepc(unsafe.Pointer(ptr),
			uintptr(n),
			getcallerpc(),
			funcPC(slicebytetostringtmp))
	}
	if msanenabled && n > 0 {
		msanread(unsafe.Pointer(ptr), uintptr(n))
	}
	stringStructOf(&str).str = unsafe.Pointer(ptr)
	stringStructOf(&str).len = n
	return
}

程序中出現問題,還是要先充分審查自己開發(fā)的代碼

到此這篇關于GoLang string類型深入分析的文章就介紹到這了,更多相關Go string內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Gorm更新零值問題解決思路與過程

    Gorm更新零值問題解決思路與過程

    這篇文章主要介紹了Gorm更新零值問題解決思路與過程,總的來說這并不是一道難題,那為什么要拿出這道題介紹?拿出這道題真正想要傳達的是解題的思路,以及不斷優(yōu)化探尋最優(yōu)解的過程。希望通過這道題能給你帶來一種解題優(yōu)化的思路
    2023-01-01
  • Golang 斷言與閉包使用解析

    Golang 斷言與閉包使用解析

    這篇文章主要介紹了Golang 斷言與閉包使用解析,Go中的斷言用于判斷變量的類型,更多相關內容需要的朋友可以參考一下
    2022-07-07
  • go中string、int、float相互轉換的實現示例

    go中string、int、float相互轉換的實現示例

    本文主要介紹了go中string、int、float相互轉換的實現示例,文中根據實例編碼詳細介紹的十分詳盡,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Go語言開發(fā)kube-scheduler整體架構深度剖析

    Go語言開發(fā)kube-scheduler整體架構深度剖析

    這篇文章主要為大家介紹了Go語言開發(fā)kube-scheduler整體架構深度剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • Golang收支記賬程序詳細編寫過程

    Golang收支記賬程序詳細編寫過程

    這篇文章主要介紹了Golang實現收支記賬程序流程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2022-12-12
  • 詳解Go語言中的作用域和變量隱藏

    詳解Go語言中的作用域和變量隱藏

    這篇文章主要為大家介紹了Go語言中的作用域和變量隱藏,文中的示例代碼講解詳細,對我們學習Go語言有一定的幫助,感興趣的小伙伴可以了解一下
    2022-04-04
  • go語言實現一個簡單的http客戶端抓取遠程url的方法

    go語言實現一個簡單的http客戶端抓取遠程url的方法

    這篇文章主要介紹了go語言實現一個簡單的http客戶端抓取遠程url的方法,實例分析了Go語言http操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-03-03
  • 使用go求冪的幾種方法小結

    使用go求冪的幾種方法小結

    這篇文章主要介紹了使用go求冪的幾種方法小結,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Golang實現簡易的命令行功能

    Golang實現簡易的命令行功能

    這篇文章主要為大家詳細介紹了如何通過Golang實現一個簡易的命令行功能,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的可以了解一下
    2023-02-02
  • golang interface{}類型轉換的實現示例

    golang interface{}類型轉換的實現示例

    在Go語言中,類型轉換可以通過斷言、顯式、隱式和強制四種方式實現,針對interface{}類型轉換為float32或float64,需要使用type斷言或reflect包處理,感興趣的可以了解一下
    2024-10-10

最新評論

龙海市| 舞钢市| 靖西县| 五台县| 高唐县| 阳山县| 黄陵县| 德兴市| 饶平县| 崇阳县| 韶山市| 伊川县| 阳新县| 乐安县| 夏津县| 同江市| 彭州市| 澄迈县| 梧州市| 含山县| 太湖县| 南皮县| 安塞县| 大渡口区| 夏河县| 永平县| 桑日县| 新田县| 南丰县| 长白| 葫芦岛市| 丰台区| 布拖县| 菏泽市| 喀什市| 夏邑县| 栾城县| 驻马店市| 武强县| 当阳市| 永寿县|