一、各數(shù)據(jù)類型內(nèi)存布局
- Sizeof(uint8) = 1 字節(jié) = 8byte
+-+
|1|
+-+
|
+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|1|
+-+-+-+-+-+-+-+-+
- Sizeof(rune) = 4
+-----+
| 4 |
+-----+
|
|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|1|0|0|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
- Sizeof(int64) = 8
+-----+
| 8 |
+-----+
|
|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|1|0|0|0|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
- Sizeof(float64)=8
+-----+
| 8 |
+-----+
|
|
同上
- Sizeof(string) = 8|16 區(qū)分32/64位服務(wù)器
var s string = "hello"
+---------+---------+
| Pointer | len=5 | # pointer和len各占4|8個字節(jié)
+---------+---------+
|
|
+---+---+---+---+---+
| h | e | l | l | o | # [5]byte
+---+---+---+---+---+
- struct
struct{a byte; b byte; c int32} = {1,2,3}
+---+---+---+---+---+---+---+---+
| 1 | 2 | 0 | 0 | 3 | 0 | 0 | 0 | # 內(nèi)存對齊原則
+---+---+---+---+---+---+---+---+
struct{a *int; b int}
+-----------+-----+
| pointer a | b |
+-----------+-----+
|
|
+-----+
| int |
+-----+
- Sizeof(slice)=24
x = []int{0,1,2,3,4,5,6,7}
+---------+---------+---------+
| Pointer | len=8 | cap=8 | # 各占8個字節(jié)
+---------+---------+---------+
|
|
+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | # [8]int
+---+---+---+---+---+---+---+---+
|
|
+---------+---------+---------+
| Pointer | len=2 | cap=5 | # x[1:3:6]
+---------+---------+---------+
- Sizeof(interface{}) = 16
var in interface{}
+---------+---------+
| *itab | *data | # 各占8個字節(jié)
+---------+---------+
| |
| |
+------+ +------+
| Itab | | data |
+------+ +------+
- new
var n = new([3]int)
+---------+
| pointer | #占8個字節(jié)
+---------+
|
|
+---+---+---+
|0 | 0 | 0 |
+---+---+---+
- make
slice = make([]int,1,3)
+---------+---------+---------+
| pointer | len=1 | cap=3 | # 各占8個字節(jié)
+---------+---------+---------+
|
|
+---+---+---+
|0 | 0 | 0 | # [3]int
+---+---+---+
channel = make(chan int);
+---------+
| pointer | # 實際返回的是一個指針包裝對象
+---------+
|
|
+---------------+
| chan.c Hchan |
+---------------+
二、基礎(chǔ)概念總結(jié)(持續(xù)更新...)
1、unsafe.sizeof總是在編譯期就進(jìn)行求值,而不是在運(yùn)行時,這意味著,sizeof的返回值可以賦值給常量。字符串類型在 go 里是個結(jié)構(gòu), 包含指向底層數(shù)組的指針和長度,這兩部分每部分都是4/ 8 個字節(jié),所以字符串類型大小為 8/16 個字節(jié)(32bit/64bit)
字符串表示一個包含0個或者多個字符序列的串,在一個字符串內(nèi)部,每個字符都表示成一個或者多個UTF-8編碼的字節(jié)。2、string 類型的值是只讀的二進(jìn)制 byte slice,不可按照索引號直接修改。字符串類型的零值是空串 "" if str =="" {true}
3、在 C/C++ 中,數(shù)組(名)是指針。將數(shù)組作為參數(shù)傳進(jìn)函數(shù)時,相當(dāng)于傳遞了數(shù)組內(nèi)存地址的引用,在函數(shù)內(nèi)部會改變該數(shù)組的值。
在 Go 中,數(shù)組是值。作為參數(shù)傳進(jìn)函數(shù)時,傳遞的是數(shù)組的原始值拷貝,此時在函數(shù)內(nèi)部是無法更新該數(shù)組的。 如果想要修改數(shù)組參數(shù),方法有兩個: 1)直接傳遞指向這個數(shù)組的指針類型。2)使用slice:即使函數(shù)內(nèi)部得到的是slice的值拷貝,但是依舊會更新slice的原始數(shù)據(jù)。4、switch 語句中的 case 代碼塊會默認(rèn)帶上 break。
5、以小寫字母開頭的字段成員是無法被外部直接訪問的。在進(jìn)行json、xml、gob等格式的encode操作時,這些私有字段容易被忽略,導(dǎo)出時得到零值。
func StrEncodeTest() {
type MyData struct {
One int
two string
}
in := MyData{1, "data"}
fmt.Printf("%#v\n", in)
en, _ := json.Marshal(in)
fmt.Println(string(en))
var out MyData
json.Unmarshal(en, &out)
fmt.Printf("%#v\n", out)
}
6、slice 中隱藏的數(shù)據(jù),從 slice 中重新切出新 slice 時,新 slice 會引用原 slice 的底層數(shù)組。如果跳了這個坑,程序可能會分配大量的臨時 slice 來指向原底層數(shù)組的部分?jǐn)?shù)據(jù),將導(dǎo)致難以預(yù)料的內(nèi)存使用。
7、select語句類似switch,但是select會公平隨機(jī)執(zhí)行選擇一個可運(yùn)行的case執(zhí)行,如果沒有可行對case,執(zhí)行default。如果沒有default,則就會阻塞。
8、使用const關(guān)鍵字定義常量,并不能使用:=標(biāo)識符。
9、從Slice移除元素
# 移除單個元素
func (s *Slice) SliceRemove(value int) {
for i, v := range *s {
if v == value {
*s = append((*s)[:i], (*s)[i+1:]...) ##slice后面‘...’ 由于元素類型相同,因此append會提示使用..., 使用兩個參數(shù)即可
}
}
}
# 移除多個元素
*s = append((*s)[:begin-1], (*s)[end:]...)
10、golang中數(shù)據(jù)類型(channel, complex, 函數(shù)) 不能轉(zhuǎn)化為有效的JSON文本。
11、關(guān)于channel的特性,下面說法正確的是(ABCD)
A. 給一個 nil channel 發(fā)送數(shù)據(jù),造成永遠(yuǎn)阻塞
B. 從一個 nil channel 接收數(shù)據(jù),造成永遠(yuǎn)阻塞
C. 給一個已經(jīng)關(guān)閉的 channel 發(fā)送數(shù)據(jù),引起 panic
D. 從一個已經(jīng)關(guān)閉的 channel 接收數(shù)據(jù),如果緩沖區(qū)中為空,則返回一個零值
- 12、For 語句中的迭代變量與閉包函數(shù)陷阱
# for語句中的迭代變量在每次迭代中都會崇勇,即for中創(chuàng)建閉包函數(shù)接受到的參數(shù)始終是同一個值,在goroutine開始執(zhí)行時都會得到同一個迭代值。
fun main(){
data := []string{"one", "two", "three"}
for _, v := range data {
go func(){ //閉包
fmt.Println(v)
}()
}
time.Sleep(3*time.Second)
}
# 輸出 three,three,three
# 解決方法:在for 內(nèi)部實用局部變量保存迭代值,在傳參:
for _, v :=range data {
vCopy := v
go func(){
fmt.Println(vCopy)
}()
}
- 13、Map 線程安全
在map并發(fā)情況下, 只讀線程是安全的,同時讀寫是線程不安全的,嚴(yán)重時導(dǎo)致程序退出,錯誤信息如下:
fatal error: concurrent map read and map write。這個錯誤信息并不是每次都會遇到,并發(fā)訪問的協(xié)成數(shù)量比較大時必然會出現(xiàn)。
所以建議使用sync.RWMutex讀寫鎖實現(xiàn)對map的并發(fā)訪問。
package main
import "sync"
type SafeMap struct {
sync.RWMutex
Map map[int]int
}
func main() {
safeMap := newSafeMap(10)
for i := 0; i < 100000; i++ {
go safeMap.writeMap(i, i)
go safeMap.readMap(i)
}
}
func newSafeMap(size int) *SafeMap {
sm := new(SafeMap)
sm.Map = make(map[int]int)
return sm
}
func (sm *SafeMap) readMap(key int) int {
sm.RLock()
value := sm.Map[key]
sm.RUnlock()
return value
}
func (sm *SafeMap) writeMap(key int, value int) {
sm.Lock()
sm.Map[key] = value
sm.Unlock()
}
通過sync.RWMutex讀寫鎖控制達(dá)到安全訪問map,此時會有一定的性能損耗。
建議使用==sync.Map== 是go1.9版本中提供的一種效率比較高并發(fā)安全。
- 14、go方法集 僅僅影響接口實現(xiàn)和方法表達(dá)式的轉(zhuǎn)化,如果指針接收者實現(xiàn)一個接口,那么只有指向那個類型的指針才能實現(xiàn)對應(yīng)的接口。 如果使用值接收者來實現(xiàn)一個接口,那么類型的值和指針都可以實現(xiàn)對應(yīng)的接口。
a) func (u *user) notify() { show(&u) }
b1) func (u user) notify() { show(u) }
b2) func (u user) notify() { show(&u) }
- 14、panic僅有最有一個可以被recover捕獲。
- 15、Array、Slice、Map、Set說明
Array:
數(shù)組是固定長度的數(shù)據(jù)類型,包含相同類型的連續(xù)元素,內(nèi)存分配是連續(xù)的。初始值為0;
一個數(shù)組可以被賦值給任意相同類型(長度和元素類型)的數(shù)組。
建議在函數(shù)中不要使用數(shù)組作為參數(shù)傳遞。
Slice:
一種動態(tài)數(shù)組,底層內(nèi)存是連續(xù)分配的。內(nèi)存結(jié)構(gòu)【指向底層數(shù)組的指針|長度|容量】;
var slice []int // nil slice
slice :=make([]int,0) //empty slice
slice :=[]int{} //empty slice
實用append方法可以對slice進(jìn)行擴(kuò)容和刪除。
函數(shù)使用slice作為參數(shù)傳遞是比較廉價的,sizeof(slice)=16字節(jié)。
Map:
Map是一種無序的K-V集合。由于是無序的,因此無法決定迭代時返回順序。
在函數(shù)使用map作為參數(shù)傳遞時,不是傳遞的map拷貝,而是引用類型,因此會修改原map的內(nèi)容的。詳情請參考:[輕松理解Go函數(shù)傳參內(nèi)幕](http://www.itdecent.cn/p/198d9961b76a)
Set:
go本身不提供set,需要自己實現(xiàn)。參考:https://github.com/deckarep/golang-set
總結(jié):array是slice和map的底層結(jié)構(gòu)。slice有cap限制,但是可以通過append增加元素,map沒有cap限制,可以無限增加。因此內(nèi)建函數(shù) cap 只能作用在 slice 上。在函數(shù)間傳遞slice和map是比較廉價的,它們不會傳遞底層數(shù)組的拷貝。
- 16、go 內(nèi)存逃逸分析
go語言編譯器會自動決定把一個變量放在棧還是放在堆,編譯器會做逃逸分析(escape analysis),當(dāng)發(fā)現(xiàn)變量的作用域沒有跑出函數(shù)范圍,就分配在棧,反之則分配在堆。
- 17、go routine 調(diào)度解析
M(machine): 代表著真正的執(zhí)行計算資源,可以認(rèn)為它就是os thread(系統(tǒng)線程)。
P(processor): 表示邏輯processor,是線程M的執(zhí)行的上下文。
G(goroutine): 調(diào)度系統(tǒng)的最基本單位goroutine,存儲了goroutine的執(zhí)行stack信息、goroutine狀態(tài)以及goroutine的任務(wù)函數(shù)等。
- 18、如何定位golang性能問題
panic 調(diào)用棧
pprof
火焰圖(配合壓測)
使用go run -race 或者 go build -race 來進(jìn)行競爭檢測
查看系統(tǒng) 磁盤IO/網(wǎng)絡(luò)IO/內(nèi)存占用/CPU 占用(配合壓測)
- 19、golang的runtime機(jī)制
Runtime 負(fù)責(zé)管理任務(wù)調(diào)度,垃圾收集及運(yùn)行環(huán)境。一個重要的組成部分goroutinue scheduler,負(fù)責(zé)追蹤調(diào)度每個routine運(yùn)行,實際上是從應(yīng)用程序的process所屬的thread pool中分配一個thread來執(zhí)行這個goroutine,每個gotine只有分配到一個os thread才能運(yùn)行。
編譯后的可執(zhí)行文件,從運(yùn)行角度看是有2個部分組成,1)用戶代碼,2)runtime。 runtime通過接口函數(shù)調(diào)用來管理goroutine,chanal等其他一些高級功能,從用戶代碼發(fā)起的調(diào)用操作系統(tǒng)的API都會被runtime攔截并處理。