成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Go 面試題:string 是線程安全的嗎?

開發(fā) 前端
在前面我們有把 StringHeader 結(jié)構(gòu)體讓大家看看,其實(shí)很明顯是不支持線程安全的。平白無(wú)故每個(gè)類型都去支持線程安全的話,會(huì)增加很多開銷。

大家好,我是煎魚。

之前在某知名平臺(tái)看到大家在交流 Go 崗位相關(guān)的面試題,其中有一道引起了大家的一些討論,勾起被八股文的深深回憶。

面試題如下:

圖片圖片

如標(biāo)題所示,原題是:Go 中的 string 賦值是線程安全的嗎?

我們可以一起先想想答案,看看中不中。

線程安全是什么

線程安全是指在多線程環(huán)境下,程序的執(zhí)行能夠正確地處理多個(gè)線程并發(fā)訪問共享數(shù)據(jù)的情況,保證程序的正確性和可靠性。

圖片圖片

能被稱之為:線程安全,需要在多個(gè)線程同時(shí)訪問共享數(shù)據(jù)時(shí),滿足如下幾個(gè)條件:

  • 不會(huì)出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)(data race):多個(gè)線程同時(shí)對(duì)同一數(shù)據(jù)進(jìn)行讀寫操作,導(dǎo)致數(shù)據(jù)不一致或未定義的行為。
  • 不會(huì)出現(xiàn)死鎖(deadlock):多個(gè)線程互相等待對(duì)方釋放資源而無(wú)法繼續(xù)執(zhí)行的情況。
  • 不會(huì)出現(xiàn)饑餓(starvation):某個(gè)線程因?yàn)橘Y源分配不公而無(wú)法得到執(zhí)行的情況。

string 線程安全

需要有一個(gè)基礎(chǔ)了解,對(duì)于 string 類型,運(yùn)行時(shí)表現(xiàn)對(duì)照是 StringHeader 結(jié)構(gòu)體。

如下:

type StringHeader struct {
   Data uintptr
   Len  int
}
  • Data:存放指針,其指向具體的存儲(chǔ)數(shù)據(jù)的內(nèi)存區(qū)域。
  • Len:字符串的長(zhǎng)度。

在了解前置知識(shí)后,接下來進(jìn)入到實(shí)踐環(huán)境。看看在 Go 里 string 類型的變量,做并發(fā)賦值到底是否線程安全。

案例一:并發(fā)訪問

我們先看第一個(gè)案例,多個(gè) goroutine 中并發(fā)訪問同一個(gè) string 變量的場(chǎng)景。如下代碼:

package main

import (
 "fmt"
 "sync"
)

func main() {
 var wg sync.WaitGroup
 str := "腦子進(jìn)煎魚了"
 for i := 0; i < 5; i++ {
  wg.Add(1)
  go func() {
   defer wg.Done()
   fmt.Println(str)
  }()
 }
 wg.Wait()
}

輸出結(jié)果:

腦子進(jìn)煎魚了
腦子進(jìn)煎魚了
腦子進(jìn)煎魚了
腦子進(jìn)煎魚了
腦子進(jìn)煎魚了

在上面的例子中,我們定義了一個(gè) string 變量 str,然后啟動(dòng)了 5 個(gè) goroutine,每個(gè) goroutine 都會(huì)輸出 str 的值。由于 str 是不可變類型,因此在多個(gè) goroutine 中并發(fā)訪問它是安全的。

可能有同學(xué)疑惑不可變類型是什么?

不可變類型,指的是一種不能被修改的數(shù)據(jù)類型,也稱為值類型(value type)。不可變類型在創(chuàng)建后其值不能被改變,任何對(duì)它的修改操作都會(huì)返回一個(gè)新的值,而不會(huì)改變?cè)械闹怠?/p>

案例二:并發(fā)寫入

第一個(gè)案例看起來沒什么問題。我們?cè)倏吹诙€(gè)案例,針對(duì)多個(gè) goroutine 并發(fā)寫入的場(chǎng)景來進(jìn)行驗(yàn)證。

如下代碼:

func main() {
 var wg sync.WaitGroup
 str := "腦子進(jìn)煎魚了"
 for i := 0; i < 5; i++ {
  wg.Add(1)
  go func() {
   defer wg.Done()
   str += "!" // 修改 str 變量
   fmt.Println(str)
  }()
 }
 wg.Wait()
}

輸出結(jié)果:

腦子進(jìn)煎魚了!
腦子進(jìn)煎魚了!!
腦子進(jìn)煎魚了!!!
腦子進(jìn)煎魚了!!!!
腦子進(jìn)煎魚了!!!!!

看起來沒什么問題,還是正常的拼接結(jié)果,輸出的順序也完全沒有問題的樣子。(大霧)

我們?cè)俣噙\(yùn)行幾次。再看看輸出結(jié)果:

// demo1
腦子進(jìn)煎魚了!
腦子進(jìn)煎魚了!!
腦子進(jìn)煎魚了!!!
腦子進(jìn)煎魚了!!!
腦子進(jìn)煎魚了!!!

// demo2
腦子進(jìn)煎魚了!
腦子進(jìn)煎魚了!!!
腦子進(jìn)煎魚了!!
腦子進(jìn)煎魚了!!!!!
腦子進(jìn)煎魚了!!!!

在上面的例子中,我們?cè)诿總€(gè) goroutine 中向 str 變量中添加了一個(gè)感嘆號(hào)。由于多個(gè) goroutine 同時(shí)修改了 str 變量,因此可能會(huì)出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)的情況。

我們會(huì)發(fā)現(xiàn)程序輸出結(jié)果會(huì)出現(xiàn)亂序或不一致的情況,可以確認(rèn) string 類型變量在多個(gè) goroutine 中是不安全的。

要警惕這種場(chǎng)景,在實(shí)際業(yè)務(wù)代碼中,常有人前人留 BUG,后人因此翻車。主打一個(gè)熬夜查和修 BUG,分分鐘還得洗臟數(shù)據(jù)。

string 實(shí)現(xiàn)線程安全

使用互斥鎖

要實(shí)現(xiàn) string 類型變量的線程安全,第一種方式:使用互斥鎖(Mutex)來保護(hù)共享變量,確保同一時(shí)間只有一個(gè) goroutine 可以訪問它。下面是一個(gè)改造后的例子。

如下代碼:

func main() {
 var wg sync.WaitGroup
 var mu sync.Mutex // 定義一個(gè)互斥鎖
 str := "煎魚"
 for i := 0; i < 5; i++ {
  wg.Add(1)
  go func() {
   defer wg.Done()
   mu.Lock() // 加鎖
   str += "!"
   fmt.Println(str)
   mu.Unlock() // 解鎖
  }()
 }
 wg.Wait()
}

輸出結(jié)果:

煎魚!
煎魚!!
煎魚!!!
煎魚!!!!
煎魚!!!!!

在上面的例子中,我們使用了 sync 包中的 Mutex 類型來定義一個(gè)互斥鎖 mu。在每個(gè) goroutine 中,我們先使用 mu.Lock() 方法來加鎖,確保同一時(shí)間只有一個(gè) goroutine 可以訪問 str 變量。

再修改 str 變量的值并輸出,最后使用 mu.Unlock() 方法來解鎖,讓其他 goroutine 可以繼續(xù)訪問 str 變量。

需要注意,互斥鎖會(huì)帶來一些性能上的開銷,兩全難齊美。

使用 atomic 包

第二種方案是使用 atomic 包來實(shí)現(xiàn)原子操作,如下代碼:

func main() {
 var wg sync.WaitGroup
 var str atomic.Value // 定義一個(gè)原子變量
 str.Store("hello, world")
 for i := 0; i < 5; i++ {
  wg.Add(1)
  go func() {
   defer wg.Done()
   oldStr := str.Load().(string) // 讀取原子變量的值
   newStr := oldStr + "!"
   str.Store(newStr) // 寫入原子變量的值
   fmt.Println(newStr)
  }()
 }
 wg.Wait()
}

這樣子也可以保證 string 類型變量的原子操作。但在現(xiàn)實(shí)場(chǎng)景下,仍然無(wú)法解決多 goroutine 導(dǎo)致的競(jìng)態(tài)條件(race condition)。

也就是存在多個(gè) goroutine 并發(fā)取到的變量值都是一樣的,得到的結(jié)果還是不固定的,最終還是要用 Mutex 或者 RWMutex 鎖來做共享變量保護(hù)。

這兩者沒有絕對(duì)的好壞,但需要分清楚你的使用場(chǎng)景,決定用鎖還是 atomic,又或是其他邏輯上的調(diào)整。

總結(jié)

在前面我們有把 StringHeader 結(jié)構(gòu)體讓大家看看,其實(shí)很明顯是不支持線程安全的。平白無(wú)故每個(gè)類型都去支持線程安全的話,會(huì)增加很多開銷。

絕大多數(shù)的情況下,你可以默認(rèn)任何數(shù)據(jù)類型的變量賦值都不是線程安全的,除非他加了鎖(Mutex)或 atomic(原子操作)。而在 string、slice、map 的并發(fā)寫導(dǎo)致出錯(cuò)的場(chǎng)景,更是每隔一段時(shí)間就能在線上看到一兩次。

每次做并發(fā)操作時(shí),都建議想清楚,這個(gè)場(chǎng)景的到底需不需要保護(hù)共享變量,做好原子操作等。

責(zé)任編輯:武曉燕 來源: 腦子進(jìn)煎魚了
相關(guān)推薦

2015-09-02 09:32:56

java線程面試

2023-07-14 08:12:21

計(jì)時(shí)器unsafecontext

2022-02-11 14:01:22

底層String字符串

2022-02-08 08:14:07

Context數(shù)據(jù)線程

2020-06-04 14:40:40

面試題Vue前端

2014-09-19 11:17:48

面試題

2021-03-12 13:57:13

零拷貝技術(shù)

2021-03-16 08:56:35

Go interface面試

2023-09-12 11:00:38

HashMap哈希沖突

2023-05-15 08:01:16

Go語(yǔ)言

2011-03-24 13:27:37

SQL

2023-11-13 07:37:36

JS面試題線程

2025-05-27 08:10:00

Go數(shù)組Map

2021-03-05 08:51:00

Go語(yǔ)言make

2021-08-05 05:04:50

熱部署模型字節(jié)

2022-01-24 07:01:20

安全多線程版本

2023-09-04 08:28:34

JavaScripforEach 循環(huán)

2023-07-05 07:30:44

StringHashMapKey類型

2020-06-24 09:55:17

Web面試前端

2022-09-05 17:49:53

Java線程池
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 亚洲精品自在在线观看 | 亚洲91精品 | 四虎最新地址 | 在线播放中文字幕 | 黄色一级大片在线免费看产 | 在线中文视频 | 亚洲精品在线看 | 亚洲欧美一区二区三区在线 | 亚洲大片一区 | 日韩中文电影 | 日本一区高清 | 国产精品日韩欧美一区二区三区 | 欧美xxxx色视频在线观看免费 | 日本三级日产三级国产三级 | 中文字幕男人的天堂 | 国产一区二区视频在线观看 | 国产一级毛片精品完整视频版 | 欧美成人精品激情在线观看 | 欧美日韩在线免费 | 欧美黄色一级毛片 | 亚洲综合视频一区 | 99国产精品99久久久久久 | 国产精品福利在线 | 欧美九九九 | 日韩中文字幕 | 国产一区二区三区四区 | 精品一区二区三区免费视频 | 精品视频久久久久久 | 波多野结衣在线观看一区二区三区 | 99免费精品视频 | 日日干干| 成年人在线观看 | 亚洲人成人一区二区在线观看 | 日本特黄a级高清免费大片 成年人黄色小视频 | 国产精品久久久久久久久久久久冷 | www国产成人 | 亚洲一级黄色 | 国产激情网 | 国产精品久久久久久一区二区三区 | 国产精品欧美一区二区三区不卡 | 成人av一区|