馬哥go運維開發5期

愛共享 愛生活 加油 2021

百度網盤git

提取碼:qhhv github

 

1.什麼是goroutine,他與process, thread有什麼區別?golang

2. 什麼是channel,爲何它能夠作到線程安全?面試

3. 瞭解讀寫鎖嗎,原理是什麼樣的,爲何能夠作到?數組

4. 如何用channel實現一個令牌桶?安全

5. 如何調試一個go程序?app

6. 如何寫單元測試和基準測試?ide

7. goroutine 的調度是怎樣的?函數

8. golang 的內存回收是如何作到的?單元測試

9. cap和len分別獲取的是什麼?

10. netgo,cgo有什麼區別?

11. 什麼是interface?

  1. uint不能直接相減,結果是負數會變成一個很大的uint,這點對動態語言出身的會可能坑。
  2. channel必定記得close。
  3. goroutine記得return或者中斷,否則容易形成goroutine佔用大量CPU。
  4. 從slice建立slice的時候,注意原slice的操做可能致使底層數組變化。
  5. 若是你要建立一個很長的slice,儘可能建立成一個slice裏存引用,這樣能夠分批釋放,避免gc在低配機器上stop the world

面試的時候儘可能瞭解協程,線程,進程的區別。

明白channel是經過註冊相關goroutine id實現消息通知的。

slice底層是數組,保存了len,capacity和對數組的引用。

若是瞭解協程的模型,就知道所謂搶佔式goroutine調用是什麼意思。

儘可能瞭解互斥鎖,讀寫鎖,死鎖等一些數據競爭的概念,debug的時候可能會有用。

儘可能瞭解golang的內存模型,知道多小纔是小對象,爲何小對象多了會形成gc壓力。

一、寫出下面代碼的輸出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package  main
 
import  "fmt"
 
func  main() {
     defer_all()
     panic( "觸發異常" )
}
 
func  defer_all()  {
     defer  func () {
         fmt.Println( "打印前" )
     }()
     defer  func () {
         fmt.Println( "打印中" )
     }()
     defer  func () {
         fmt.Println( "打印後" )
     }()
}

  解析:這道題主要考察的是對 defer 的理解,defer 主要是延遲函數,延遲到調用者函數執行 return 命令以前,

多個 defer 以前按照先進後出的順序執行,因此,這道題中,在 panic 觸發時結束函數運行,在 return 以前依次打

印:打印後、打印中、打印前。最後 runtime 運行時拋出打印 panic 異常信息,panic 須要 defer 結束後纔會向上傳

        須要注意的是,函數的 return value 不是原子操做,而是在編譯器中被分解成兩部分:返回值和return,而咱們

知道 defer 是在 return 以前執行的,因此能夠在 defer 函數中修改返回值,以下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package  main
 
import  (
     "fmt"
)
 
func  main() {
     fmt.Println(doubleScore(0))    //0
     fmt.Println(doubleScore(20.0)) //40
     fmt.Println(doubleScore(50.0)) //50
}
func  doubleScore(source float32) (score float32) {
     defer  func () {
         if  score < 1 || score >= 100 {
             //將影響返回值
             score = source
         }
     }()
     score = source * 2
     return
 
     //或者
     //return source * 2
}

2. 下面的代碼輸出什麼?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package  main
 
import  "fmt"
 
func  calc(index string, a, b int) int {
     ret := a + b
     fmt.Println(index, a, b, ret)
     return  ret
}
 
func  main()  {
     a := 1
     b := 2
     defer  calc( "1" , a, calc( "10" , a, b))
     a = 0
     defer  calc( "2" , a, calc( "20" , a, b))
     b = 1
}

 解析:

   程序在執行到第三行的時候,會先執行 calc 函數的 b 參數,即:calc("10",a,b),輸出:10,1,2,3 獲得值 3,而後由於

defer 定義的函數是延遲函數故 calc("1",1,3) 會被延遲執行

   程序執行到第五行的時候,一樣先執行 calc("20",a,b) 輸出:20,0,2,2 獲得值 2,一樣將 calc("2",0,2) 延遲執行

   程序執行到末尾的時候,按照棧先進後出的方式依次執行:calc("2",0,2),calc("1",1,3),則就依次輸出:2,0,2,二、

1,1,3,4

3.請寫出如下輸出內容

1
2
3
4
5
func  main() {
     s := make([]int, 5)
     s = append(s,1,2,3)
     fmt.Println(s)
}

  解析:

     使用 make 初始化 slice,第二個參數表明的是 slice 的長度,slice 還有第三個參數表示容量,這裏沒有指定容量表示建立一個

滿容的切片,使用 len()、cap() 函數獲取切片的 長度,初始化後切片的長度和容量都是 5,使用 append 追加三個元素使得切片的

長度大於原有的容量,此時切片的容量擴大一倍,變成 10,所以輸出的結果爲:

1
[0 0 0 0 0 1 2 3]

3. 下面的代碼能正常編譯嗎?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package  main
 
import  (
     "fmt"
)
 
type  People interface  {
     Speak(string) string
}
 
type  Stduent struct {}
 
func  (stu *Stduent) Speak(think string) (talk string) {
     if  think == "bitch"  {
         talk = "You are a good boy"
     } else  {
         talk = "hi"
     }
     return
}
 
func  main() {
     var  peo People = Stduent{}
     think := "bitch"
     fmt.Println(peo.Speak(think))
}

  運行打印結果:

1
2
3
# command-line-arguments
.\main. go :23:6: cannot use Stduent literal ( type  Stduent) as type  People in assignment:
         Stduent does not implement People (Speak method has pointer receiver)

  從上面的輸出信息能夠看出 Student 沒有實現 People 這個接口

       解析:

       咱們來看一下語言規範裏面定義的規則,這些規則用來講明一個類型的值或指針是否實現了該接口:

       1.類型 *T 的可調用方法集包含接收者爲 *T 或 T 的全部方法集

          這條規則說的是若是咱們用來調用接口方法的變量是一個指針類型,那麼方法的接收者能夠是值類型也能夠是指針類型,

如今看一下咱們的例子顯然是不符合規則的,var peo People = Student{} 是一個值類型

       2. 類型 T 的可調用方法集包含接收者爲 T 的全部方法集

         這條規則說的是若是咱們用來調用接口方法的變量是一個值類型,那麼方法的接收者必需要是值類型才能夠被調用,看一下

咱們的例子,方法的接收者是指針類型

        上面的代碼能夠這樣修改:

        1.var peo People = &Student{}

        2.將方法的接收者改爲值類型

        能夠參考這篇文章:https://github.com/Unknwon/gcblog/blob/master/content/26-methods-interfaces-and-embedded-types-in-golang.md

4. 下面的代碼是死循環嗎?

1
2
3
4
5
6
func main() {
     v := []int{1, 2, 3}
     for  i := range v {
         v = append(v, i)
     }
}

  解析:

       直接運行上面的代碼咱們會發現上面的程序不會出現死循環,可以正常結束。 

       咱們來看一下切片的 for range ,它的底層代碼是:

1
2
3
4
5
6
7
8
//   for_temp := range
//   len_temp := len(for_temp)
//   for index_temp = 0; index_temp < len_temp; index_temp++ {
//           value_temp = for_temp[index_temp]
//           index = index_temp
//           value = value_temp
//           original body
//   }

  從底層代碼中咱們能夠看到在遍歷 slice 以前會先計算 slice 的長度做爲循環次數,循環體中,每次循環會先獲取

元素值,若是 for-range 中接收 index,value 則會對 index,value 進行一次賦值

       因爲循環開始前循環次數已經肯定了,因此循環過程當中新添加的元素沒辦法遍歷到 

       參考文章:https://my.oschina.net/renhc/blog/2396058

 5.下面的代碼有什麼問題嗎?

1
2
3
4
5
6
7
8
9
10
slice := []int{0, 1, 2, 3}
myMap := make(map[int]*int)
 
for  index, value := range slice {
     myMap[index] = &value
}
fmt.Println( "=====new map=====" )
for  k, v := range myMap {
     fmt.Printf( "%d => %d\n" , k, *v)
}

  運行打印輸出結果:

1
2
3
4
5
===== new  map=====
3 => 3
0 => 3
1 => 3
2 => 3

  結果徹底同樣,都是最後一次遍歷的值。經過第 4 道題目對切片 for-range 的底層代碼可知,遍歷後

的值賦值給 value,在咱們的例子中,會把 value 的地址保存到 myMap 中,這裏的 value 是一個全局變量,

&value 取得是這個全局變量的地址,因此最後輸出的結果都是同樣的而且是最後一個值,至關於以下代碼:

1
2
3
4
5
6
7
8
9
     // for_temp := range
// len_temp := len(for_temp)
// for index_temp = 0;index_temp < len_temp;index_temp++ {
//     value_temp = for_temp[index_temp]
//     index = index_temp
//     value = value_temp
//     myMap[index] = &value
//     original body
// }

       注意:這裏必須是保存指針纔會有問題,若是直接保存的是 value,不會有問題

       總結:經過 for-range 遍歷切片,首先,計算遍歷的次數(切片的長度);每次遍歷,都會把當前遍歷到的值

存放到一個全局變量中

相關文章
相關標籤/搜索