<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Golang中slice和map并發(fā)寫入問題解決

          2022-05-26 12:36

          本篇文章為大家分享在Golang中,如何實現(xiàn)對slice和map兩種數(shù)據(jù)類型進(jìn)行并發(fā)寫入。對于入門Golang的開發(fā)者來說,可能無法意識到這個問題,這里也會做一個問題演示。

          關(guān)于Golang更多互聯(lián)網(wǎng)大廠面試問題,點擊訪問。

          切片類型

          同步寫入

          在下面的代碼中,我們使用for循環(huán)同步模式對一個切片進(jìn)行追加操作。通過結(jié)果可以得出,是預(yù)期的效果。

          func?main()?{
          ?var?slice?[]string

          ?for?i?:=?0;?i?9999;?i++?{
          ??slice?=?append(slice,?"demo")
          ?}

          ?fmt.Println("slice?len",?len(slice))
          }
          //?output
          slice?len?9999

          多協(xié)程寫入

          在很多時候,我們?yōu)榱颂岣卟l(fā)能力,會開啟多協(xié)程模式對切片寫入,如下代碼:

          func?main()?{
          ?var?slice?[]string

          ?for?i?:=?0;?i?9999;?i++?{
          ??go?func()?{
          ???slice?=?append(slice,?"demo")
          ??}()
          ?}

          ?fmt.Println("slice?len",?len(slice))
          }
          //?output
          ╰─?go?run?demo1.go
          slice?len?7680?//?第一次結(jié)果
          slice?len?8168?//?第二次結(jié)果
          slice?len?7913?//?第三次結(jié)果

          通過上圖可以看出,實際追加的數(shù)據(jù)不是我們預(yù)期的結(jié)果。

          原理分析

          1. 在同步模式下,是一個阻塞式寫入過程。每循環(huán)一次,往切片中追加一個元素,追完完畢之后在進(jìn)行下一次循環(huán)。因此,不會出現(xiàn)追加的元素不正確情況。如下圖:
          1. 多協(xié)程寫入下,是一個并發(fā)式寫入過程。我們無法保證每一次的寫都是有序的,存在第一個協(xié)程向某個索引位寫入數(shù)據(jù)之后,后執(zhí)行的協(xié)程同樣的往這個索引位寫入數(shù)據(jù),就導(dǎo)致前面的協(xié)程寫入數(shù)據(jù)被后面的協(xié)程給覆蓋掉。如下圖:

          協(xié)程20得到的索引位和協(xié)程5得到鎖因為是同一個,則協(xié)程20將協(xié)程5寫入的數(shù)據(jù)變成了20。協(xié)程100與協(xié)程6也是同樣原理。因此上述代碼和預(yù)期結(jié)果是有偏差的。

          解決方案

          通過上述的原理分析,知道了多協(xié)程寫入存在的問題。該如何解決呢?其實我們可以采用上述的同步模式進(jìn)行寫,保證每一個協(xié)程的寫入是有序的就可以了。要解決該問題,我們可以使用鎖。

          1. 每次進(jìn)行循環(huán)時,開啟一把鎖。對切片進(jìn)行寫入數(shù)據(jù)。
          2. 對切片寫入之后,釋放鎖。進(jìn)行下次循環(huán)。

          示例代碼如下:

          func?main()?{
          ?var?slice?[]string
          ?mutex?:=?sync.RWMutex{}

          ?for?i?:=?0;?i?9999;?i++?{
          ??mutex.Lock()
          ??go?func()?{
          ???slice?=?append(slice,?"demo")
          ???mutex.Unlock()
          ??}()
          ?}

          ?fmt.Println("slice?len",?len(slice))
          }
          //?output
          slice?len?9998

          這種方案,其實也不難看出存在問題。1是每次循環(huán)都開啟一把鎖,循環(huán)完釋放鎖,這樣性能低。2是最終的結(jié)果是少一個寫入操作。如果對應(yīng)解決方案的可以留言提供解決方案。

          map類型

          map并發(fā)式寫入數(shù)據(jù),同樣會出現(xiàn)問題。但不會像切片那種直接被覆蓋,而是直接會拋出異常。

          func?main()?{
          ?mapInfo?:=?make(map[int]string)

          ?for?i?:=?0;?i?10;?i++?{
          ??go?func(index?int)?{
          ???mapInfo[index]?=?"demo"
          ??}(i)
          ?}

          ?fmt.Println(len(mapInfo))
          }

          拋出如下異常:

          fatal?error:?concurrent?map?writes
          fatal?error:?concurrent?map?writes

          goroutine?6?[running]:
          runtime.throw(0x10ca607,?0x15)
          ?/usr/local/go/src/runtime/panic.go:1117?+0x72?fp=0xc000034f60?sp=0xc000034f30?pc=0x10327d2
          runtime.mapassign_fast64(0x10b1ee0,?0xc000054030,?0x0,?0x0)
          ?/usr/local/go/src/runtime/map_fast64.go:176?+0x325?fp=0xc000034fa0?sp=0xc000034f60?pc=0x1010c25
          main.main.func1(0xc000054030,?0x0)

          Golang默認(rèn)map是不支持并發(fā)寫入操作。

          解決方案

          要對map做并發(fā)寫入,則需要使用互斥鎖來實現(xiàn),實現(xiàn)并發(fā)讀、同步寫。在使用官方的sync包,有兩種方案,第一種是sync.RWMutex,第二種是sync.map。

          sync.RWMutex包實現(xiàn)

          func?main()?{
          ?mapInfo?:=?make(map[int]string)
          ?mutex?:=?sync.RWMutex{}

          ?//?使用for循環(huán)模擬多個請求對map進(jìn)行寫操作。
          ?for?i?:=?0;?i?10000;?i++?{
          ??mutex.Lock()
          ??go?func(index?int,?mapInfo?map[int]string)?{
          ???mapInfo[index]?=?"demo"
          ???mutex.Unlock()
          ??}(i,?mapInfo)
          ?}

          ?fmt.Println(len(mapInfo))

          ?//?正常寫法
          ?mapInfo?:=?make(map[int]string)
          ?mutex?:=?sync.RWMutex{}
          ?mutex.Lock()
          ?mapInfo[0]?=?"demo"
          ?mutex.Unlock()
          }

          上述代碼,也可以使用匿名結(jié)構(gòu)體的方式進(jìn)行編寫。

          func?main()?{
          ?var?counter?=?struct?{
          ??sync.RWMutex
          ??mapInfo?map[int]string
          ?}{mapInfo:?make(map[int]string)}
          ?
          ?for?i?:=?0;?i?10000;?i++?{
          ??counter.Lock()
          ??go?func(index?int,?mapInfo?map[int]string)?{
          ???mapInfo[index]?=?"demo"
          ???counter.Unlock()
          ??}(i,?counter.mapInfo)
          ?
          ?}
          ?fmt.Println(len(counter.mapInfo))
          }

          使用sync.RWMutex包實現(xiàn),能解決并發(fā)寫入map問題。當(dāng)寫數(shù)據(jù)很多時,開啟一把鎖會導(dǎo)致其他的協(xié)程處于阻塞等待過程中,會導(dǎo)致整體的并發(fā)能力降低。

          sync.map包實現(xiàn)

          官方在新版本中推薦使用sync.Map來實現(xiàn)并發(fā)寫入操作。sync.Map核心思想是減少鎖,使用空間換取時間。該包實現(xiàn)如下幾個優(yōu)化點:

          1. 空間換時間。通過冗余的兩個數(shù)據(jù)結(jié)構(gòu)(read、dirty),實現(xiàn)加鎖對性能的影響。
          2. 使用只讀數(shù)據(jù)(read),避免讀寫沖突。
          3. 動態(tài)調(diào)整,miss次數(shù)多了之后,將dirty數(shù)據(jù)提升為read。
          4. double-checking。
          5. 延遲刪除。刪除一個鍵值只是打標(biāo)記,只有在提升dirty的時候才清理刪除的數(shù)據(jù)。
          6. 優(yōu)先從read讀取、更新、刪除,因為對read的讀取不需要鎖。
          var?sy?sync.Map

          func?main()?{
          ?sy.Store("name",?"tom")

          ?sy.Range(func(key,?value?interface{})?bool?{
          ??fmt.Println(key,?value)
          ??return?false
          ?})
          }
          //?outpt
          name?tom

          更多關(guān)于sync.Map,可以參考該文章


          瀏覽 187
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  WWW日本色情视频 | 99国产精品久久久久久久久久久久久 | 黄色国产在线观看 | 狠狠躁日日躁XXXXAAAA | 美女特黄A级 |