golang 中的 slice 數據類型,是利用指針指向某個連續片斷的數組。
一個 slice
在 golang 中佔用24個 bytesgolang
a = make([]int, 0) unsafe.Sizeof(a) // 24 var c int unsafe.Sizeof(c) // 8, 一個 int 在 golang 中佔用 8 個bytes(本機是64位操做系統)
在 runtime 的 slice.go 中,定義了 slice 的 struct編程
type slice struct { array unsafe.Pointer // 8 bytes len int // 8 bytes cap int // 8 bytes // 確認了,slice 的大小 24 }
簡單準備一段程序,看看 golang 是如何初始化一個切片的數組
package main import "fmt" func main() { a := make([]int, 0) a = append(a, 2, 3, 4) fmt.Println(a) }
使用 dlv
調試,反彙編後:sass
(dlv) disassemble TEXT main.main(SB) /Users/such/gomodule/runtime/main.go main.go:5 0x10b70f0 65488b0c2530000000 mov rcx, qword ptr gs:[0x30] main.go:5 0x10b70f9 488d4424e8 lea rax, ptr [rsp-0x18] main.go:5 0x10b70fe 483b4110 cmp rax, qword ptr [rcx+0x10] main.go:5 0x10b7102 0f8637010000 jbe 0x10b723f main.go:5 0x10b7108* 4881ec98000000 sub rsp, 0x98 main.go:5 0x10b710f 4889ac2490000000 mov qword ptr [rsp+0x90], rbp main.go:5 0x10b7117 488dac2490000000 lea rbp, ptr [rsp+0x90] main.go:6 0x10b711f 488d051a0e0100 lea rax, ptr [rip+0x10e1a] main.go:6 0x10b7126 48890424 mov qword ptr [rsp], rax main.go:6 0x10b712a 0f57c0 xorps xmm0, xmm0 main.go:6 0x10b712d 0f11442408 movups xmmword ptr [rsp+0x8], xmm0 main.go:6 0x10b7132 e8b99af8ff ** call $runtime.makeslice ** main.go:6 0x10b7137 488b442418 mov rax, qword ptr [rsp+0x18] main.go:6 0x10b713c 4889442460 mov qword ptr [rsp+0x60], rax main.go:6 0x10b7141 0f57c0 xorps xmm0, xmm0 main.go:6 0x10b7144 0f11442468 movups xmmword ptr [rsp+0x68], xmm0 ...
在一堆指令中,看到 call $runtime.makeslice
的調用應該是初始化 slice安全
func makeslice(et *_type, len, cap int) unsafe.Pointer { mem, overflow := math.MulUintptr(et.size, uintptr(cap)) if overflow || mem > maxAlloc || len < 0 || len > cap { // NOTE: Produce a 'len out of range' error instead of a // 'cap out of range' error when someone does make([]T, bignumber). // 'cap out of range' is true too, but since the cap is only being // supplied implicitly, saying len is clearer. // See golang.org/issue/4085. mem, overflow := math.MulUintptr(et.size, uintptr(len)) if overflow || mem > maxAlloc || len < 0 { panicmakeslicelen() } panicmakeslicecap() } return mallocgc(mem, et, true) }
makeslice 最後返回真正值存儲的數組域的內存地址,函數中 uintptr()
是什麼呢?併發
println(uintptr(0), ^uintptr(0)) // 0 18446744073709551615 爲何按位異或後是這個數? var c int = 1 println(^c, ^uint64(0)) // -2 18446744073709551615
從這幾行代碼驗證,有符號的1,二進制爲:0001,異或後:1110,最高位1是負數,表示-2;
uint64二進制:0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
異或後:1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
由於無符號的,轉換成10進制,就是 2 ^ 64 - 1 = 18446744073709551615
。因此,其實^uintptr(0) 就是指當前機器(32位,uint32;64位,uint64)的最大值。
咱們能夠打印下如今的 a
app
(dlv) p a []int len: 1, cap: 0, [0]
=> main.go:7 0x10b7149 eb00 jmp 0x10b714b main.go:7 0x10b714b 488d0dee0d0100 lea rcx, ptr [rip+0x10dee] main.go:7 0x10b7152 48890c24 mov qword ptr [rsp], rcx main.go:7 0x10b7156 4889442408 mov qword ptr [rsp+0x8], rax main.go:7 0x10b715b 0f57c0 xorps xmm0, xmm0 main.go:7 0x10b715e 0f11442410 movups xmmword ptr [rsp+0x10], xmm0 main.go:7 0x10b7163 48c744242003000000 mov qword ptr [rsp+0x20], 0x3 main.go:7 0x10b716c e84f9bf8ff call $runtime.growslice main.go:7 0x10b7171 488b442428 mov rax, qword ptr [rsp+0x28] main.go:7 0x10b7176 488b4c2430 mov rcx, qword ptr [rsp+0x30] main.go:7 0x10b717b 488b542438 mov rdx, qword ptr [rsp+0x38] main.go:7 0x10b7180 4883c103 add rcx, 0x3 main.go:7 0x10b7184 eb00 jmp 0x10b7186 main.go:7 0x10b7186 48c70002000000 mov qword ptr [rax], 0x2 main.go:7 0x10b718d 48c7400803000000 mov qword ptr [rax+0x8], 0x3 main.go:7 0x10b7195 48c7401004000000 mov qword ptr [rax+0x10], 0x4 main.go:7 0x10b719d 4889442460 mov qword ptr [rsp+0x60], rax main.go:7 0x10b71a2 48894c2468 mov qword ptr [rsp+0x68], rcx main.go:7 0x10b71a7 4889542470 mov qword ...
在對 slice 作 append 的時候,實際上是調用了 call runtime.growslice
,看看作了什麼:函數
func growslice(et *_type, old slice, cap int) slice { if cap < old.cap { panic(errorString("growslice: cap out of range")) } if et.size == 0 { // append should not create a slice with nil pointer but non-zero len. // We assume that append doesn't need to preserve old.array in this case. return slice{unsafe.Pointer(&zerobase), old.len, cap} } newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.len < 1024 { newcap = doublecap } else { for 0 < newcap && newcap < cap { newcap += newcap / 4 } if newcap <= 0 { newcap = cap } } } var overflow bool var lenmem, newlenmem, capmem uintptr // Specialize for common values of et.size. // For 1 we don't need any division/multiplication. // For sys.PtrSize, compiler will optimize division/multiplication into a shift by a constant. // For powers of 2, use a variable shift. switch { case et.size == 1: lenmem = uintptr(old.len) newlenmem = uintptr(cap) capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem) case et.size == sys.PtrSize: lenmem = uintptr(old.len) * sys.PtrSize newlenmem = uintptr(cap) * sys.PtrSize capmem = roundupsize(uintptr(newcap) * sys.PtrSize) overflow = uintptr(newcap) > maxAlloc/sys.PtrSize newcap = int(capmem / sys.PtrSize) case isPowerOfTwo(et.size): var shift uintptr if sys.PtrSize == 8 { // Mask shift for better code generation. shift = uintptr(sys.Ctz64(uint64(et.size))) & 63 } else { shift = uintptr(sys.Ctz32(uint32(et.size))) & 31 } lenmem = uintptr(old.len) << shift newlenmem = uintptr(cap) << shift capmem = roundupsize(uintptr(newcap) << shift) overflow = uintptr(newcap) > (maxAlloc >> shift) newcap = int(capmem >> shift) default: lenmem = uintptr(old.len) * et.size newlenmem = uintptr(cap) * et.size capmem, overflow = math.MulUintptr(et.size, uintptr(newcap)) capmem = roundupsize(capmem) newcap = int(capmem / et.size) } if overflow || capmem > maxAlloc { panic(errorString("growslice: cap out of range")) } var p unsafe.Pointer if et.ptrdata == 0 { // 申請內存 p = mallocgc(capmem, nil, false) // 清除未使用的地址 memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem) } else { p = mallocgc(capmem, et, true) if lenmem > 0 && writeBarrier.enabled { bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem) } } // 拷貝大小爲 lenmem 個btyes,從old.array到p memmove(p, old.array, lenmem) return slice{p, old.len, newcap}
具體擴容的策略:ui
newcap += newcap / 4
,知道 newcap 不小於要申請的容量,若是溢出,newcap = cap(要申請的容量)擴容完成後就開始根據 t.size 的大小,從新計算地址,其中新 slice 的 len
爲原 slice 的 cap
(只有 slice 的 len 超過 cap,才須要擴容)。
接着申請 capmem
大小的內存,從 old.array 拷貝 lenmem
個 bytes (就是原 slice 整個拷貝,lenmem 就是計算的原切片的大小)到 p
。this
a := make([]int, 0) a = append(a, 1) println("1 times:", len(a), cap(a)) // 1 times: 1 1 a = append(a, 2, 3) println("2 times:", len(a), cap(a)) // 2 times: 3 4 a = append(a, 4) println("3 times:", len(a), cap(a)) // 3 times: 4 4
能夠看出:
append
後的 len
大於 cap
的2倍,即擴大至大於 len
的第一個2的倍數append
後的 len
大於 cap
且小於 cap
的兩倍,cap
擴大至2倍append
後的 len
小於 cap
,直接追加使用 slice
,也許不知不覺中就會形成一些問題。
a := []int{1, 2, 3, 4, 5} shadow := a[1:3] shadow = append(shadow, 100) fmt.Println(shadow, a) // [2 3 100] [1 2 3 100 5]
結果很意外,但也是符合邏輯。a 的結構體中 array
是指向數組 [1,2,3,4,5]
的內存地址,shadow
是指向其中 [2,3]
的內存地址。在向 shadow
增長後,會直接修改真實的數組,間接影響到指向數組的全部切片。因此能夠修改上述代碼爲:
a := []int{1, 2, 3, 4, 5} shadow := append([]int{}, a[1:3]...) shadow = append(shadow, 100) fmt.Println(shadow, a) // [2 3 100] [1 2 3 4 5]
若是某個函數的返回值,是上述的這種狀況 return a[1:3]
,還會形成 [1,2,3,4,5]
鎖佔用的內存沒法釋放。
知道了 slice
自己是指向真實的數組的指針,在 Golang
中提供了 unsafe
來作指針操做。
a := []int{1, 2, 3, 4, 5} shadow := a[1:3] shadowPtr := uintptr(unsafe.Pointer(&shadow[0])) offset := unsafe.Sizeof(int(0)) fmt.Println(*(*int)(unsafe.Pointer(shadowPtr - offset))) // 1 fmt.Println(*(*int)(unsafe.Pointer(shadowPtr + 2*offset))) // 4
shadowPtr
是 a 的第1個下標的位置,一個 int
在64位機器上是8 bytes,向前偏移1個 offset
,是 a 的第0個下標 1;向後偏移2個 offset
,是 a 的第3個下標 4。
slice
是非協程安全的數據類型,若是建立多個 goroutine
對 slice
進行併發讀寫,會形成丟失。看一段代碼
package main import ( "fmt" "sync" ) func main () { a := make([]int, 0) var wg sync.WaitGroup for i := 0; i < 10000; i++ { wg.Add(1) go func(i int) { a = append(a, i) wg.Done() }(i) } wg.Wait() fmt.Println(len(a)) } // 9403 9876 9985 9491 ...
屢次執行,每次獲得的結果都不同,總之必定不會是想要的 10000 個。想要解決這個問題,按照協程安全的編程思想來考慮問題,
能夠考慮使用 channel
自己的特性(阻塞)來實現安全的併發讀寫。
func main() { a := make([]int, 0) buffer := make(chan int) go func() { for v := range buffer { a = append(a, v) } }() var wg sync.WaitGroup for i := 0; i < 10000; i++ { wg.Add(1) go func(i int) { buffer <- i wg.Done() }(i) } wg.Wait() fmt.Println(len(a)) } // 10000