解決golang中container/list包中的坑
golang中l(wèi)ist包用法可以參看這篇文章
但是list包中大部分對(duì)于e *Element進(jìn)行操作的元素都可能會(huì)導(dǎo)致程序崩潰,其根本原因是e是一個(gè)Element類(lèi)型的指針,當(dāng)然其也可能為nil,但是golang中l(wèi)ist包中函數(shù)沒(méi)有對(duì)其進(jìn)行是否為nil的檢查,變默認(rèn)其非nil進(jìn)行操作,所以這種情況下,便可能出現(xiàn)程序崩潰。
1.舉個(gè)簡(jiǎn)單例子
Remove()函數(shù)
package main import ( "container/list" "fmt" ) func main() { l := list.New() l.PushBack(1) fmt.Println(l.Front().Value) //1 value := l.Remove(l.Front()) fmt.Println(value) //1 value1 := l.Remove(l.Front()) //panic: runtime error: invalid memory address or nil pointer dereference fmt.Println(value1) }
從程序中可以直觀的看出程序崩潰,原因是list中只有1個(gè)元素,但是要?jiǎng)h除2個(gè)元素。但是再進(jìn)一步查看一下原因,便會(huì)得出如下結(jié)果。
golang中Front()函數(shù)實(shí)現(xiàn)如下
func (l *List) Front() *Element { if l.len == 0 { return nil } return l.root.next }
由此可見(jiàn),當(dāng)?shù)谝淮蝿h除之后。list的長(zhǎng)度變?yōu)?,此時(shí)在調(diào)用l.Remove(l.Front()),其中l(wèi).Front()返回的是一個(gè)nil。
接下來(lái)再看golang中Remove()函數(shù)實(shí)現(xiàn),該函數(shù)并沒(méi)有判定e是否為nil,變直接默認(rèn)其為非nil,直接對(duì)其進(jìn)行e.list或者e.Value取值操作。
當(dāng)e為nil時(shí),這兩個(gè)操作都將會(huì)造成程序崩潰,這也就是為什么上面程序會(huì)崩潰的原因。
func (l *List) Remove(e *Element) interface{} { if e.list == l { // if e.list == l, l must have been initialized when e was inserted // in l or l == nil (e is a zero Element) and l.remove will crash l.remove(e) } return e.Value }
2.(l *list)PushBackList(other *list)
該函數(shù)用于將other list中元素添加在l list的后面。
基本實(shí)現(xiàn)思想是取出other中所有元素,將其順次掛載在l列表中,但是golang中實(shí)現(xiàn)有問(wèn)題
代碼如下
func (l *List) PushBackList(other *List) { l.lazyInit() for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() { l.insertValue(e.Value, l.root.prev) } }
其具體思想是首先獲取other的長(zhǎng)度n,然后循環(huán)n次取出其元素將其插入l中。問(wèn)題就出現(xiàn)在循環(huán)n次,如果在這個(gè)過(guò)程中other的元素變化的話,例如其中有些元素被刪除了,這就導(dǎo)致e的指針可能為nil,此時(shí)再利用e.Value取值,程序便會(huì)崩潰。
如下所示
package main import ( "container/list" "runtime" ) func main() { runtime.GOMAXPROCS(8) l := list.New() ls := list.New() for i := 0; i < 10000; i++ { ls.PushBack(i) } go ls.Remove(l.Back()) l.PushBackList(ls) //invalid memory address or nil pointer dereference }
如程序中所示,再講ls中元素添加到l過(guò)程中,如果ls中元素減少,程序便會(huì)崩潰。原因如上面分析。
建議:
在golang中如果對(duì)與list的操作只有串行操作,則只需要注意檢查元素指針是否為nil便可避免程序崩潰,如果程序中會(huì)并發(fā)處理list中元素,建議對(duì)list進(jìn)行加寫(xiě)鎖(全局鎖),然后再操作。注意,讀寫(xiě)鎖無(wú)法保證并行處理list時(shí)程序的安全性。
補(bǔ)充:golang list 鏈表
看代碼吧~
package main import ( "container/list" "fmt" ) func main() { dataList := list.New() dataList.PushBack(1) // 插入末尾 dataList.PushBack(2) dataList.PushFront(3) // 插入表頭 dataList.PushBack(4) dataList.PushBack(5) m := dataList.PushBack(6) m1 := dataList.InsertBefore(7,m) // 6 之前插入 7 m2 := dataList.InsertAfter(8,m) // 6 之后插入 8 // 從鏈表頭開(kāi)始遍歷 for e := dataList.Front(); e != nil; e = e.Next() { fmt.Println(e.Value) // 打印值 } fmt.Println("----------------------------------------") dataList.Remove(dataList.Front()) // 移除頭部 dataList.MoveBefore(m2, m) // 將m2移動(dòng)m之前 dataList.MoveAfter(m1, m) dataList.Remove(m) // 移除 //PushBackList // 插入列表 //PushFrontList // // 從鏈表頭開(kāi)始遍歷 for e := dataList.Front(); e != nil; e = e.Next() { fmt.Println(e.Value) // 打印值 } fmt.Println("----------------------------------------") // 從鏈表尾開(kāi)始遍歷 for e := dataList.Back(); e != nil; e = e.Prev() { fmt.Println(e.Value, " ") } fmt.Println("----------------------------------------") dataList.Init() // 清空鏈表 // 從鏈表頭開(kāi)始遍歷 for e := dataList.Front(); e != nil; e = e.Next() { fmt.Println(e.Value) // 打印值 } }
運(yùn)行結(jié)果:
3
1
2
4
5
7
6
8
----------------------------------------
1
2
4
5
8
7
----------------------------------------
7
8
5
4
2
1
----------------------------------------Process finished with exit code 0
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
golang cobra使用chatgpt qdrant實(shí)現(xiàn)ai知識(shí)庫(kù)
這篇文章主要為大家介紹了golang cobra使用chatgpt qdrant實(shí)現(xiàn)ai知識(shí)庫(kù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09golang程序使用alpine編譯出最小arm鏡像實(shí)現(xiàn)
這篇文章主要為大家介紹了golang程序使用alpine編譯出最小arm鏡像,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12Golang?throttled基于GCRA速率限制庫(kù)使用探索
這篇文章主要為大家介紹了Golang?throttled基于GCRA速率限制庫(kù)使用實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01GoLang BoltDB數(shù)據(jù)庫(kù)詳解
這篇文章主要介紹了GoLang BoltDB數(shù)據(jù)庫(kù),boltdb是使用Go語(yǔ)言編寫(xiě)的開(kāi)源的鍵值對(duì)數(shù)據(jù)庫(kù),boltdb存儲(chǔ)數(shù)據(jù)時(shí) key和value都要求是字節(jié)數(shù)據(jù),此處需要使用到 序列化和反序列化2023-02-02Go語(yǔ)言數(shù)據(jù)類(lèi)型簡(jiǎn)單介紹
這篇文章主要介紹了Go語(yǔ)言數(shù)據(jù)類(lèi)型簡(jiǎn)單介紹的相關(guān)資料,需要的朋友可以參考下2023-08-08Go和Java算法詳析之分?jǐn)?shù)到小數(shù)
這篇文章主要給大家介紹了關(guān)于Go和Java算法詳析之分?jǐn)?shù)到小數(shù)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-08-08