天天看點

Go面經

前段時間一直在找工作,将自己的Go總結分享出來,期待大家交流~

目錄

  • 1.time.Now()傳回的是什麼時間?這樣做的決定因素是什麼?
  • 2.選擇三個常見 golang 元件
  • 3.noCopy
  • 4.sync.Pool源碼分析
  • 5.請列舉兩種不同的觀察 GC 占用 CPU 程度的方法,觀察方法無需絕對精确,但需要可實際運用于 profiling 和 optimization。
  • 6.JSON 标準庫對 nil slice 和 空 slice 的處理是一緻的嗎
  • 7.string與[]byte轉換
  • 8.模拟gdb調試
  • 9.INT_MAX值
  • 10.rune與byte
  • 11.字元串拼接
  • 12.類型比較
  • 13.格式化輸出
  • 15.切片擴容
  • 16.有無緩沖區chan
  • 17.協程洩漏
  • 18.Go 可以限制運作時作業系統線程的數量嗎? 常見的goroutine操作函數有哪些?
  • 19.defer底層原理
  • 20.make和new
  • 21.panic和recover
  • 22.map
  • 23.context
  • 25.接口
  • 26.reflect反射
  • 27.http
  • 28.主協程如何優雅等待子協程
  • 29.Go中map如何順序讀取
  • 30.變量位址
  • 31.goto
  • 32.type alias
  • 33.讀取大檔案

1.time.Now()傳回的是什麼時間?這樣做的決定因素是什麼?

Wall clock(time) VS Monotonic clock(time)

Wall clock(time)就是我們一般意義上的時間,就像牆上鐘所訓示的時間。

Monotonic clock(time)字面意思是單調時間,實際上它指的是從某個點開始後(比如系統啟動以後)流逝的時間,jiffies一定是單調遞增的!

而特别要強調的是計算兩個時間點的內插補點一定要用Monotonic clock(time),因為Wall clock(time)是可以被修改的,比如計算機時間被回撥(比如校準或者人工回撥等情況),或者閏秒( leap second),會導緻兩個wall clock(time)可能出現負數。(因為作業系統或者上層應用不一定完全支援閏秒,出現閏秒後系統時間會在後續某個點會調整為正确值,就有可能出現時鐘回撥(當然也不是一定,比如ntpdate就有可能出現時鐘回撥,但是ntpd就不會))

2.選擇三個常見 golang 元件

channel, goroutine, [], map, sync.Map等,

列舉它們常見的嚴重傷害性能的 anti-pattern?

select 很多 channel 的時候,并發較高時會有性能問題。因為 select 本質是按 chan 位址排序,順序加鎖。lock1->lock2->lock3->lock4 活躍 goroutine 數量較多時,會導緻全局的延遲不可控。比如 99 分位慘不忍睹。slice append 的時候可能觸發擴容,初始 cap 不合适會有大量 growSlice。map hash collision,會導緻 overflow bucket 很長,但這種基本不太可能,hash seed 每次啟動都是随機的。此外,map 中 key value 超過 128 位元組時,會被轉為 indirectkey 和 indirectvalue,會對 GC 的掃描階段造成壓力,如果 k v 多,則掃描的 stw 就會很長。sync.Map 有啥性能問題?寫多讀少的時候?

3.noCopy

noCopy原理:

type noCopy struct {
}

func (*noCopy) Lock()   {}
func (*noCopy) Unlock() {}      

4.sync.Pool源碼分析

 https://xiaorui.cc/archives/5878
  • Get 的整個過程就非常清晰了:

首先,調用 p.pin() 函數将目前的 goroutine 和 P 綁定,禁止被搶占,傳回目前 P 對應的 poolLocal,以及 pid。然後直接取 l.private,指派給 x,并置 l.private 為 nil。判斷 x 是否為空,若為空,則嘗試從 l.shared 的頭部 pop 一個對象出來,同時指派給 x。如果 x 仍然為空,則調用 getSlow 嘗試從其他 P 的 shared 雙端隊列尾部“偷”一個對象出來。Pool 的相關操作做完了,調用 runtime_procUnpin() 解除非搶占。最後如果還是沒有取到緩存的對象,那就直接調用預先設定好的 New 函數,建立一個出來。

  • Put 的邏輯也很清晰:

先綁定 g 和 P,然後嘗試将 x 指派給 private 字段。如果失敗,就調用 pushHead 方法嘗試将其放入 shared 字段所維護的雙端隊列中

5.請列舉兩種不同的觀察 GC 占用 CPU 程度的方法,觀察方法無需絕對精确,但需要可實際運用于 profiling 和 optimization。

  • pprof 的 cpuprofile 火焰圖 啟動時傳入環境變量 Debug=gctrace
  • go tool trace,可以看到 p 綁定的 g 實際的 GC 動作和相應時長,以及阻塞時間

6.JSON 标準庫對 nil slice 和 空 slice 的處理是一緻的嗎

package main

import (
    "encoding/json"
    "log"
)

type S struct {
    A []string
}

func main() {
    data := &S{}
    data2 := &S{A: []string{}}
    buf, err := json.Marshal(&data)
    log.Println(string(buf), err)
    buf2, err2 := json.Marshal(&data2)
    log.Println(string(buf2), err2)
}      

輸出:

2009/11/10 23:00:00 {"A":null} <nil>
2009/11/10 23:00:00 {"A":[]} <nil>      

7.string與[]byte轉換

結論:string與[]byte轉換過程中的Data都會發生拷貝,例如:

// 可能的結果:
// 1374390312984
// 1374390312984 1374390230744
// 說明,string to slice,一定發生内容copy
func checkToSlice() {
 str := fmt.Sprint(rand.Int63())
 strHeader := (*reflect.StringHeader)(unsafe.Pointer(&str))
 fmt.Println(strHeader.Data)

 toSlice := []byte(str)
 sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&toSlice))
 fmt.Println(strHeader.Data, sliceHeader.Data)
}

// 可能的結果:
// 1374390152904
// 1374390152904 1374390152872
// 說明,slice to string,一定發生内容copy
func checkToString() {
 slice := []byte(fmt.Sprint(rand.Int63()))
 sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
 fmt.Println(sliceHeader.Data)

 toStr := string(slice)
 strHeader := (*reflect.StringHeader)(unsafe.Pointer(&toStr))
 fmt.Println(sliceHeader.Data, strHeader.Data)
}      

像如下代碼會進行優化。

func Equal(a, b []byte) bool {
 // Neither cmd/compile nor gccgo allocates for these string conversions.
 return string(a) == string(b)
}      
  • []byte轉string沒問題 提供的String方法就是将[]byte轉換為string類型,這裡為了避免記憶體拷貝的問題,使用了強制轉換來避免記憶體拷貝:
func (b *Builder) String() string {
 return *(*string)(unsafe.Pointer(&b.buf))
}      
  • string轉[]byte有問題 這種直接轉換有風險,string隻有raw ptr和length,沒有capacity的概念,當reslice或者append等操作時,需要capacity,此時會panic。
func NoAllocBytes(buf string) []byte {
 return *(*[]byte)(unsafe.Pointer(&buf))
}      

正确做法:

func NoAllocBytes(buf string) []byte {
 x := (*reflect.StringHeader)(unsafe.Pointer(&buf))
 h := reflect.SliceHeader{x.Data, x.Len, x.Len}
 return *(*[]byte)(unsafe.Pointer(&h))
}      

8.模拟gdb調試

​​https://github.com/go-delve/delve​​

brew install dlv/yum install dlv      

9.INT_MAX值

const maxInt = int(^uint(0) >> 1)      

10.rune與byte

rune = uint32 
byte = uint8      

11.字元串拼接

  • 使用+操作符進行拼接時,會對字元串進行周遊,計算并開辟一個新的空間來存儲原來的兩個字元串。
  • fmt 由于采用了接口參數,必須要用反射擷取值,是以有性能損耗。
  • strings.Builder 用WriteString()進行拼接,内部實作是指針+切片,同時String()傳回拼接後的字元串,它是直接把[]byte轉換為string,進而避免變量拷貝。strings.Builder使用問題,内部有addr(是否指向自身,copyCheck檢查),buf([]byte數組,存放内容,寫入時append方式)。

注意點:兩個strings.Builder禁止在寫入之後拷貝,寫入之前可以拷貝,如下例子,主要原因是寫入會觸發copyCheck檢查。

var b1 strings.Builder
b1.WriteString("ABC")
b2 := b1
// b2.WriteString("DEF") 失敗 在寫操作會進行copyCheck -> 如果記憶體空 會操作addr,不空則會判斷位址是否一緻
fmt.Println(b1, b2)
var b3, b4 strings.Builder
b4 = b3 // 一開始都為空 是以可以進行copy
b3.WriteString("123")
b4.WriteString("456")
fmt.Println(b3.String(), b4.String())      
  • bytes.Buffer bytes.Buffer是一個一個緩沖byte類型的緩沖器,這個緩沖器裡存放着都是byte,包含一個offset變量,控制尾部寫入資料,從頭部讀取資料,
  • strings.Join 基于strings.Builder建構,支援分隔符拼接,内部調用時會先Grow, 以提前進行容量配置設定可以減少記憶體配置設定,很高效。strings.Join ≈ strings.Builder > bytes.Buffer > "+" > fmt.Sprintf

12.類型比較

Go 結構體有時候并不能直接比較,當其基本類型包含:slice、map、function 時,是不能比較的。若強行比較,就會導緻出現例子中的直接報錯的情況。reflect.DeepEqual來判斷不可比較類型,如:map、slice、func等

type Value struct {
    Name   string
    Gender *string
}

func main() {
    v1 := Value{Name: "1", Gender: new(string)}
    v2 := Value{Name: "1", Gender: new(string)}
    if v1 == v2 {
        fmt.Println("yes")
        return
    }

    fmt.Println("no")
}      

輸出:no,因為位址不一樣

13.格式化輸出

%v、%+v、%#v %v輸出結構體各成員的值;%+v輸出結構體各成員的名稱和值;%#v輸出結構體名稱和結構體各成員的名稱和值

%v的方式  = &{test 123456} %+v的方式 = &{name:test id:123456} %#v的方式 = &main.student{name:"test", id:123456}

14.Go垃圾回收機制

垃圾回收機制是Go一大特(nan)色(dian)。Go1.3采用标記清除法, Go1.5采用三色标記法,Go1.8采用三色标記法+混合寫屏障。

标記清除法(mark and sweep):

分為兩個階段:标記和清除 第一步,暫停程式業務邏輯, 分類出可達和不可達的對象 第二步, 開始标記,程式找出它所有可達的對象,并做上标記 第三步,  标記完了之後,然後開始清除未标記的對象. 第四步, 停止暫停,讓程式繼續跑。然後循環重複這個過程,直到process程式生命周期結束

缺點:

  • STW,stop the world;讓程式暫停,程式出現卡頓
  • 标記需要掃描整個heap;
  • 清除資料會産生heap碎片。早期優化,縮短STW時間,原來STW包括:啟動STW、标記、清除、停止STW,優化為啟動STW、标記、停止STW,清除操作與程式并行,而不被包含在STW過程中。

三色标記法:

第一步 , 每次新建立的對象,預設的顔色都是标記為“白色”, 第二步, 每次GC回收開始, 會從根節點開始周遊所有對象,把周遊到的對象從白色集合放入"灰色"集合。第三步, 周遊灰色集合,将灰色對象引用的對象從白色集合放入灰色集合,之後将此灰色對象放入黑色集合。第四步, 重複第三步, 直到灰色中無任何對象。第五步: 回收所有的白色标記表的對象. 也就是回收垃圾。我們将全部的白色對象進行删除回收,剩下的就是全部依賴的黑色對象。

不加STW,會遇到對象丢失問題:

  • 條件1: 一個白色對象被黑色對象引用(白色被挂在黑色下)
  • 條件2: 灰色對象與它之間的可達關系的白色對象遭到破壞(灰色同時丢了該白色) 如果當以上兩個條件同時滿足時,就會出現對象丢失現象!

屏障機制:

強弱三色不變式:
1.強三色不變式
強制不允許黑色對象引用白色對象,目的在于破壞條件1。
2.弱三色不變式
黑色對象允許引用白色對象,白色對象存在其他灰色對象引用,或者可達它的鍊路上存在灰色對象,目的在于破壞條件2。      

是以,在三色标級中滿足強三色不變式或弱三色不變式之一,即可保證對象不丢失。

1.插入屏障 (為了保證棧的速度,不在棧上使用) 
具體操作: 在A對象引用B對象的時候,B對象被标記為灰色。(将B挂在A下遊,B必須被标記為灰色)
滿足: 強三色不變式. (不存在黑色對象引用白色對象的情況了, 因為白色會強制變成灰色)
插入屏障不會在棧上操作,堆上處理沒問題,但是如果棧不添加,當全部三色标記掃描之後,棧上有可能依然存在白色對象被引用的情況(黑色引用白色對象).  是以要對棧重新進行三色标記掃描, 但這次為了對象不丢失, 要對本次标記掃描啟動STW暫停. 直到棧空間的三色标記結束.
是以插入屏障,最後會對棧上的所有對象進行一次三色标記法+STW保護。
2. 删除屏障
具體操作: 被删除的對象,如果自身為灰色或者白色,那麼被标記為灰色。
滿足: 弱三色不變式. (保護灰色對象到白色對象的路徑不會斷)
這種方式的回收精度低,一個對象即使被删除了最後一個指向它的指針也依舊可以活過這一輪,在下一輪GC中被清理掉。      

Go V1.8的混合寫屏障(hybrid write barrier)機制

插入寫屏障和删除寫屏障的短闆:

  • 插入寫屏障:結束時需要STW來重新掃描棧,标記棧上引用的白色對象的存活;-  删除寫屏障:回收精度低,GC開始時STW掃描堆棧來記錄初始快照,這個過程會保護開始時刻的所有存活對象。Go V1.8版本引入了混合寫屏障機制(hybrid write barrier),避免了對棧re-scan的過程,極大的減少了STW的時間。結合了兩者的優點。1)混合寫屏障規則 1、GC開始将棧上的對象全部掃描并标記為黑色(之後不再進行第二次重複掃描,無需STW), 2、GC期間,任何在棧上建立的新對象,均為黑色。3、被删除的對象标記為灰色。4、被添加的對象标記為灰色。滿足: 變形的弱三色不變式. 這裡我們注意, 屏障技術是不在棧上應用的,因為要保證棧的運作效率。

15.切片擴容

  • 如果期望容量大于目前容量的兩倍就會使用期望容量;
  • 如果目前切片的長度小于 1024 就會将容量翻倍;
  • 如果目前切片的長度大于 1024 就會每次增加 25% 的容量,直到新容量大于期望容量;

16.有無緩沖區chan

有緩沖的channel是異步的,而無緩沖channel是同步的。

17.協程洩漏

  • 缺少接收器,導緻發送阻塞
  • 缺少發送器,導緻接收阻塞
  • 死鎖。多個協程由于競争資源導緻死鎖。
  • WaitGroup Add()和Done()不相等,前者更大。

18.Go 可以限制運作時作業系統線程的數量嗎?常見的goroutine操作函數有哪些?

runtime三大函數:runtime.Gosched()、runtime.Goexit()、runtime.GOMAXPROCS() 可以,使用runtime.GOMAXPROCS(num int)可以設定線程數目。該值預設為CPU邏輯核數,如果設的太大,會引起頻繁的線程切換,降低性能。runtime.Gosched(),用于讓出CPU時間片,讓出目前goroutine的執行權限,排程器安排其它等待的任務運作,并在下次某個時候從該位置恢複執行。runtime.Goexit(),調用此函數會立即使目前的goroutine的運作終止(終止協程),而其它的goroutine并不會受此影響。runtime.Goexit在終止目前goroutine前會先執行此goroutine的還未執行的defer語句。請注意千萬别在主函數調用runtime.Goexit,因為會引發panic。

19.defer底層原理

  • 後調用的 defer 函數會先執行:
  • 後調用的 defer 函數會被追加到 Goroutine _defer 連結清單的最前面;
  • 運作 runtime._defer 時是從前到後依次執行;defer nil -> panic defer運作時就會立刻将傳遞給延遲函數的參數儲存起來 for loop defer need a func wrap channel
  • 同步 Channel — 不需要緩沖區,發送方會直接将資料交給(Handoff)接收方;
  • 異步 Channel — 基于環形緩存的傳統生産者消費者模型;
  • chan struct{} 類型的異步 Channel — struct{} 類型不占用記憶體空間,不需要實作緩沖區和直接發送(Handoff)的語義;runtime.hchan包含緩沖區指針、元素個數、循環隊列長度、發送操作處理到的位置、 接收操作處理到的位置、收發元素類型/大小、sendq 和 recvq 存儲了目前 Channel 由于緩沖區空間不足而阻塞的 Goroutine 列 makechan
  • 如果目前 Channel 中不存在緩沖區,那麼就隻會為 runtime.hchan 配置設定一段記憶體空間;
  • 如果目前 Channel 中存儲的類型不是指針類型,會為目前的 Channel 和底層的數組配置設定一塊連續的記憶體空間;
  • 在預設情況下會單獨為 runtime.hchan 和緩沖區配置設定記憶體;發送資料的過程中包含幾個會觸發 Goroutine 排程的時機:
  • 發送資料時發現 Channel 上存在等待接收資料的 Goroutine,立刻設定處理器的 runnext 屬性,但是并不會立刻觸發排程;
  • 發送資料時并沒有找到接收方并且緩沖區已經滿了,這時會将自己加入 Channel 的 sendq 隊列并調用 runtime.goparkunlock 觸發 Goroutine 的排程讓出處理器的使用權;從 Channel 接收資料時,會觸發 Goroutine 排程的兩個時機:
  • 當 Channel 為空時;
  • 當緩沖區中不存在資料并且也不存在資料的發送者時;

chan panic觸發時機:

  • 向已經關閉的channel寫。
  • 關閉已經關閉的channel。

chan 阻塞觸發時機:

無緩存channel:

  • 通道中無資料,但執行讀通道。
  • 通道中無資料,向通道寫資料,但無協程讀取。

有緩存channel:

  • 通道的緩存無資料,但執行讀通道。
  • 通道的緩存已經占滿,向通道寫資料,但無協程讀。

20.make和new

make 和 new 關鍵字的實作原理,make 關鍵字的作用是建立切片、哈希表和 Channel 等内置的資料結構,而 new 的作用是為類型申請一片記憶體空間,并傳回指向這片記憶體的指針。

21.panic和recover

panic 函數可以被連續多次調用,它們之間通過 link 可以組成連結清單,内部包含:argp 是指向 defer 調用時參數的指針;arg 是調用 panic 時傳入的參數;link 指向了更早調用的 runtime._panic 結構;recovered 表示目前 runtime._panic 是否被 recover 恢複;aborted 表示目前的 panic 是否被強行終止;

  • 編譯器會負責做轉換關鍵字的工作;将 panic 和 recover 分别轉換成 runtime.gopanic 和 runtime.gorecover;○ 将 defer 轉換成 runtime.deferproc 函數;○ 在調用 defer 的函數末尾調用 runtime.deferreturn 函數;
  • 在運作過程中遇到 runtime.gopanic 方法時,會從 Goroutine 的連結清單依次取出 runtime._defer 結構體并執行;
  • 如果調用延遲執行函數時遇到了 runtime.gorecover 就會将 _panic.recovered 标記成 true 并傳回 panic 的參數;在這次調用結束之後,runtime.gopanic 會從 runtime._defer 結構體中取出程式計數器 pc 和棧指針 sp 并調用 runtime.recovery 函數進行恢複程式;○ runtime.recovery 會根據傳入的 pc 和 sp 跳轉回 runtime.deferproc;○ 編譯器自動生成的代碼會發現 runtime.deferproc 的傳回值不為 0,這時會跳回 runtime.deferreturn 并恢複到正常的執行流程;
  • 如果沒有遇到 runtime.gorecover 就會依次周遊所有的 runtime._defer,并在最後調用 runtime.fatalpanic 中止程式、列印 panic 的參數并傳回錯誤碼 2;

22.map

  • 開放位址法 線性探測,裝載因子=元素數量/數組長度

index := hash("author") % array.len

  • 拉鍊法 裝載因子:=元素數量/桶數量 實作拉鍊法一般會使用數組加上連結清單,不過一些程式設計語言會在拉鍊法的哈希中引入紅黑樹以優化性能,拉鍊法會使用連結清單數組作為哈希底層的資料結構,我們可以将它看成可以擴充的二維數組 在一般情況下使用拉鍊法的哈希表裝載因子都不會超過 1,當哈希表的裝載因子較大時會觸發哈希的擴容,建立更多的桶來存儲哈希中的元素,保證性能不會出現嚴重的下降。如果有 1000 個桶的哈希表存儲了 10000 個鍵值對,它的性能是儲存 1000 個鍵值對的 1/10,但是仍然比在連結清單中直接讀寫好 1000 倍。1.建立map
  • 計算哈希占用的記憶體是否溢出或者超出能配置設定的最大值;
  • 調用 runtime.fastrand 擷取一個随機的哈希種子;
  • 根據傳入的 hint 計算出需要的最小需要的桶的數量;
  • 使用 runtime.makeBucketArray 建立用于儲存桶的數組;2.擴容
  • 裝載因子已經超過 6.5;
  • 哈希使用了太多溢出桶;根據觸發的條件不同擴容的方式分成兩種,如果這次擴容是溢出的桶太多導緻的,那麼這次擴容就是等量擴容 sameSizeGrow,sameSizeGrow 是一種特殊情況下發生的擴容,當我們持續向哈希中插入資料并将它們全部删除時,如果哈希表中的資料量沒有超過門檻值,就會不斷積累溢出桶造成緩慢的記憶體洩漏4。runtime: limit the number of map overflow buckets 引入了 sameSizeGrow 通過複用已有的哈希擴容機制解決該問題,一旦哈希中出現了過多的溢出桶,它會建立新桶儲存資料,垃圾回收會清理老的溢出桶并釋放記憶體

哈希在存儲元素過多時會觸發擴容操作,每次都會将桶的數量翻倍,擴容過程不是原子的,而是通過 runtime.growWork 增量觸發的,在擴容期間通路哈希表時會使用舊桶,向哈希表寫入資料時會觸發舊桶元素的分流。除了這種正常的擴容之外,為了解決大量寫入、删除造成的記憶體洩漏問題,哈希引入了 sameSizeGrow 這一機制,在出現較多溢出桶時會整理哈希的記憶體減少空間的占用。

哈希表的每個桶都隻能存儲 8 個鍵值對,一旦目前哈希的某個桶超出 8 個,新的鍵值對就會存儲到哈希的溢出桶中。随着鍵值對數量的增加,溢出桶的數量和哈希的裝載因子也會逐漸升高,超過一定範圍就會觸發擴容,擴容會将桶的數量翻倍,元素再配置設定的過程也是在調用寫操作時增量進行的,不會造成性能的瞬時巨大抖動。

23.context

1.Context接口

Deadline()
Done()
Err()
Value()      
  • context.Background 是上下文的預設值,所有其他的上下文都應該從它衍生出來;
  • context.TODO 應該僅在不确定應該使用哪種上下文時使用;context.Background與context.TODO實作都一緻,都是emptyCtx(int列名類型) 2.WithCancel從context.Context衍生出一個新的子上下文并傳回用于取消該上下文的函數 WithCancel傳回一個bool,CancelFunc。CancelFunc為匿名函數,内部調用cancelCtx的cancel方法。cancelCtx結構裡面包含了:
type cancelCtx struct {
 Context // 傳入的context

 mu       sync.Mutex            // 保護接下來的三個字段
 done     chan struct{}         // created lazily, closed by first cancel call
 children map[canceler]struct{} // set to nil by the first cancel call
 err      error                 // set to non-nil by the first cancel call
}      

一旦我們執行傳回的取消函數,目前上下文以及它的子上下文都會被取消,所有的 Goroutine 都會同步收到這一取消信号 3.WithValue WithValue從父上下文中建立一個子上下文,傳回valueCtx

type valueCtx struct {
 Context
 key, val interface{}
}      

重寫Value方法,從父上下文中擷取 val 4.WithDeadline WithDeadline建立可以被取消的計時器上下文timerCtx

type timerCtx struct {
 cancelCtx
 timer *time.Timer // Under cancelCtx.mu.

 deadline time.Time
}      

context.WithDeadline 在建立 context.timerCtx 的過程中判斷了父上下文的截止日期與目前日期,并通過 time.AfterFunc 建立定時器,當時間超過了截止日期後會調用 context.timerCtx.cancel 同步取消信号。context.timerCtx 内部不僅通過嵌入 context.cancelCtx 結構體繼承了相關的變量和方法,還通過持有的定時器 timer 和截止時間 deadline 實作了定時取消的功能 5.WithDeadline WithTimeout會調用WithDeadline

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
 return WithDeadline(parent, time.Now().Add(timeout))
}      

24.數組與切片的差別 數組是值類型,定長數組, StringHeader

type StringHeader struct {
 Data uintptr
 Len  int
}      

切片是引用類型,動态指向數組的指針,不定長指向數組array, SliceHeader

type SliceHeader struct {
 Data uintptr
 Len  int
 Cap  int
}      

25.接口

Go 語言根據接口類型是否包含一組方法将接口類型分成了兩類:

  • 使用 runtime.iface 結構體表示包含方法的接口
type iface struct { // 16 位元組
 tab  *itab
 data unsafe.Pointer
}      
  • 使用 runtime.eface 結構體表示不包含任何方法的 interface{} 類型;
type eface struct { // 16 位元組
 _type *_type
 data  unsafe.Pointer
}      

接口類型interface{}

type emptyInterface struct {
 typ  *rtype
 word unsafe.Pointer
}      

26.reflect反射

  • reflect.TypeOf能擷取類型資訊 reflect.TypeOf 的實作原理其實并不複雜,它隻是将一個 interface{} 變量轉換成了内部的 reflect.emptyInterface 表示,然後從中擷取相應的類型資訊。
  • reflect.ValueOf能擷取到資料的運作時資訊 reflect.Type為接口,reflect.Value為結構體 私有成員,對外暴露公共方法。我們通過 reflect.TypeOf、reflect.ValueOf 可以将一個普通的變量轉換成反射包中提供的 reflect.Type 和 reflect.Value,随後就可以使用反射包中的方法對它們進行複雜的操作。用于擷取接口值 reflect.Value 的函數 reflect.ValueOf 實作也非常簡單,在該函數中我們先調用了 reflect.escapes 保證目前值逃逸到堆上,然後通過 reflect.unpackEface 從接口中擷取 reflect.Value 結構體。

原則1: 接口到任意對象,入參均是interface{},出參為任意對象。

原則2: 反射對象擷取interface{}變量。

可以通過reflect.Value.Interface完成這項工作。

v := reflect.ValueOf(1)
v.Interface().(int)      

上述兩個原則轉換:

  • 從接口值到反射對象:○ 從基本類型到接口類型的類型轉換;○ 從接口類型到反射對象的轉換;
  • 從反射對象到接口值:○ 反射對象轉換成接口類型;○ 通過顯式類型轉換變成原始類型;
  • 調用 reflect.ValueOf 擷取變量指針;
  • 調用 reflect.Value.Elem 擷取指針指向的變量;
  • 調用 reflect.Value.SetInt 更新變量的值:
func main() {
 i := 1
 v := reflect.ValueOf(&i)
 v.Elem().SetInt(10)
 fmt.Println(i)
}      

27.http

按照HTTP/1.1的規範,Go http包的http server和client的實作預設将所有連接配接視為長連接配接,無論這些連接配接上的初始請求是否帶有Connection: keep-alive。1.http.Client 如果要關閉keep-alive, 需要通過http.Transport設定:

tr := &http.Transport{  DisableKeepAlives: true, } c := &http.Client{  Transport: tr, }

// 發送請求 rsp, err:= c.Do(req) defer rsp.Body.Close() // 讀取資料 ioutil.ReadAll(rsp.Body)

2.http.Server server端完全不支援keep-alive連接配接方式:

s := http.Server{  Addr: ":8080",  Handler: xxx, } s.SetKeepAliveEnabled(false) s.ListenAndServe()

閑置連接配接逾時控制:

s := http.Server{     Addr: ":8080",     Handler: xxx,  IdleTimeout: 5 * time.Second, } s.ListenAndServe()

用戶端和服務端響應的次數

  • 長連接配接:可以多次。
  • 短連結:一次。傳輸資料的方式
  • 長連接配接:連接配接--資料傳輸--保持連接配接
  • 短連接配接:連接配接--資料傳輸--關閉連接配接 長連接配接和短連結的優缺點
  • 長連接配接 優點 省去較多的TCP建立和關閉的操作,進而節約時間。■ 性能比較好。(因為用戶端一直和服務端保持聯系) ○ 缺點 當用戶端越來越多的時候,會将伺服器壓垮。■ 連接配接管理難。■ 安全性差。(因為會一直保持着連接配接,可能會有些無良的用戶端,随意發送資料等)
  • 短連結 優點 服務管理簡單。存在的連接配接都是有效連接配接 ○ 缺點 請求頻繁,在TCP的建立和關閉操作上浪費時間

28.主協程如何優雅等待子協程

  • channel進行同步
  • sync.WaitGroup同步

29.Go中map如何順序讀取

30.變量位址

package main

const cl  = 100

var bl    = 123

func main()  {
    println(&bl,bl)
    println(&cl,cl) // 不可取cl位址
}      

31.goto

package main

func main()  {

    for i:=0;i<10 ;i++  {
    loop:
        println(i)
    }
    goto loop
}      

32.type alias

type aType  int // defintion
type bType = int // alias
var i int
var i1 aType = i // 報錯 需要強轉
var i2 bType = i // 可以直接指派      

33.讀取大檔案

f, err := os.Open(xxx)
bufio.NewReader(f) // 預設 4k
buf := bufio.NewReaderSize(f, xxx)
for {
 line, prefix, err := buf.ReadLine()
 // dosomething
 if err != nil {
  if err == io.EOF {
   return nil
  }
  return err
 }
}