[TOC]
## new 和 make 是什么,差異在哪?
`make`僅支持`slice`、`map`、`channel`三種數據類型的內存創建,**其返回值是所創建類型的本身,而不是新的指針引用**
~~~
func make(t Type, size ...IntegerType) Type
~~~
`new`可以對類型進行內存創建和初始化。**其返回值是所創建類型的指針引用**
~~~
func new(Type) *Type
~~~
總結:
`make`函數:
* 能夠**分配并初始化**類型所需的內存空間和結構,返回引用類型的本身。
* 具有使用范圍的局限性,僅支持`channel`、`map`、`slice`三種類型。
* 具有獨特的優勢,`make`函數會對三種類型的內部數據結構(長度、容量等)賦值。
`new`函數:
* 能夠**分配**類型所需的內存空間,返回指針引用(指向內存的指針)。
* 可被替代,能夠通過字面值快速初始化。
## GMP模型
### 基礎
G:Goroutine,實際上我們每次調用`go func`就是生成了一個 G。
P:Processor,處理器,一般 P 的數量就是處理器的核數,可以通過`GOMAXPROCS`進行修改。
M:Machine,系統線程。
這三者交互實際來源于 Go 的 M: N 調度模型。也就是 M 必須與 P 進行綁定,然后不斷地在 M 上循環尋找可運行的 G 來執行相應的任務。
### 原理
https://mp.weixin.qq.com/s/uWP2X6iFu7BtwjIv5H55vw
### Goroutine 數量控制在多少合適,會影響 GC 和調度?
這個先說一下gmp模型,再說一下限制
* M:有限制,默認數量限制是 10000,可調整。
* G:沒限制,但受內存影響。
~~~
假設一個 Goroutine 創建需要 4k:
4k * 80,000 = 320,000k ≈ 0.3G內存
4k * 1,000,000 = 4,000,000k ≈ 4G內存
以此就可以相對計算出來一臺單機在通俗情況下,所能夠創建 Goroutine 的大概數量級別。
注:Goroutine 創建所需申請的 2-4k 是需要連續的內存塊。
~~~
* P:受本機的核數影響,可大可小,不影響 G 的數量創建。
## interface
https://mp.weixin.qq.com/s/vSgV_9bfoifnh2LEX0Y7cQ

## GMP模型為什么要由P?
go1.0沒有P,存在如下問題:
1、每個 M 都需要做內存緩存(M.mcache)
~~~
會導致資源消耗過大(每個 mcache 可以吸納到 2M 的內存緩存和其他緩存),數據局部性差
~~~
2、存在單一的全局 mutex(Sched.Lock)和集中狀態管理
~~~
mutex 需要保護所有與 goroutine 相關的操作(創建、完成、重排等),導致鎖競爭嚴重。
~~~
3、頻繁的線程阻塞/解阻塞
~~~
在存在 syscalls 的情況下,線程經常被阻塞和解阻塞。這增加了很多額外的性能開銷
~~~
有了P之后:
1、大幅度的減輕了對全局隊列的直接依賴,所帶來的效果就是鎖競爭的減少。而 GM 模型的性能開銷大頭就是鎖競爭。
2、每個 P 相對的平衡上,在 GMP 模型中也實現了 Work Stealing 算法,如果 P 的本地隊列為空,則會從全局隊列或其他 P 的本地隊列中竊取可運行的 G 來運行,減少空轉,提高了資源利用率。
## 結構體是否能被比較
當基礎類型存在slice、map、function,是不能比較的,
~~~
切片之間是不能比較的,我們不能使用`==`操作符來判斷兩個切片是否含有全部相等元素。 切片唯一合法的比較操作是和`nil`比較
~~~
## G0和M0
### m0
m0 是 Go Runtime 所創建的第一個系統線程,一個 Go 進程只有一個 m0,也叫主線程。
從多個方面來看:
* 數據結構:m0 和其他創建的 m 沒有任何區別。
* 創建過程:m0 是進程在啟動時應該匯編直接復制給 m0 的,其他后續的 m 則都是 Go Runtime 內自行創建的。
* 變量聲明:m0 和常規 m 一樣,m0 的定義就是`var m0 m`,沒什么特別之處。
### g0
g 一般分為三種,分別是:
* 執行用戶任務的叫做 g。
* 執行`runtime.main`的 main goroutine。
* 執行調度任務的叫 g0。。
g0 比較特殊,每一個 m 都只有一個 g0(僅此只有一個 g0),且每個 m 都只會綁定一個 g0。在 g0 的賦值上也是通過匯編賦值的,其余后續所創建的都是常規的 g。
從多個方面來看:
* 數據結構:g0 和其他創建的 g 在數據結構上是一樣的,但是存在棧的差別。在 g0 上的棧分配的是系統棧,在 Linux 上棧大小默認固定 8MB,不能擴縮容。而常規的 g 起始只有 2KB,可擴容。
* 運行狀態:g0 和常規的 g 不一樣,沒有那么多種運行狀態,也不會被調度程序搶占,調度本身就是在 g0 上運行的。
* 變量聲明:g0 和常規 g,g0 的定義就是`var g0 g`,沒什么特別之處。
## Go是值傳遞還是引用傳遞?
值傳遞
傳值:**指的是在調用函數時將實際參數復制一份傳遞到函數中**,這樣在函數中如果對參數進行修改,將不會影響到實際參數
傳引用:**指在調用函數時將實際參數的地址直接傳遞到函數中**,那么在函數中對參數所進行的修改,將影響到實際參數
map 和 slice 的行為類似于指針,它們是包含指向底層 map 或 slice 數據的指針的描述符
~~~
chan:返回的指針
makechan(t *chantype,size int64) *hchan{}
map:返回的指針
makemap(t *maptype,hint int,h *hmap)*hmp
~~~
## Go是如何實現面向對象的
封裝、繼承、多態
封裝:隱藏對象的內部屬性和實現細節,僅對外提供公開接口調用
在 Go 語言中的屬性訪問權限,通過首字母大小寫來控制:
* 首字母大寫,代表是公共的、可被外部訪問的。
* 首字母小寫,代表是私有的,不可以被外部訪問。
~~~
type Animal struct {
name string
}
func NewAnimal() *Animal {
return &Animal{}
}
func (p *Animal) SetName(name string) {
p.name = name
}
func (p *Animal) GetName() string {
return p.name
}
~~~
繼承:指的是子類繼承父類的特征和行為,使得子類對象(實例)具有父類的實例域和方法,或子類從父類繼承方法,使得子類具有父類相同的行為。
在go語言中是通過嵌套結構體來實現的
~~~
type Dog struct {
Animal
jiao string
}
func main() {
//an := NewAnimal()
//an.Name = "旺財"
dog := Dog{
Animal: Animal{
Name: "旺財",
},
jiao: "wangwang~",
}
dog.SetName("旺財1")
fmt.Println(dog)//{{旺財1} wangwang~}
}
~~~
多態:指的同一個行為具有多種不同表現形式或形態的能力,具體是指一個類實例(對象)的相同方法在不同情形有不同表現形式。
~~~
type AnimalSounder interface {
MakeDNA()
}
func MakeSomeDNA(animalSounder AnimalSounder) {
animalSounder.MakeDNA()
}
func (c *Cat) MakeDNA() {
fmt.Println("喵~喵~喵~")
}
func (c *Dog) MakeDNA() {
fmt.Println("汪~汪~汪~")
}
func main() {
MakeSomeDNA(&Cat{})
MakeSomeDNA(&Dog{})
}
~~~
## 什么是協程,協程和線程的區別和聯系?
**進程**:一個具有特定功能的程序運行在一個數據集上的一次動態過程。是操作系統資源分配的最小單位。
~~~
進程是為了壓榨cpu的性能,但是可能執行的不是計算型的任務,可能是網絡調用,單進程直接阻塞了,cpu就空閑了,就出現了多進程;
需要線程的原因:
* 進程間的信息難以共享數據,父子進程并未共享內存,需要通過進程間通信(IPC),在進程間進行信息交換,性能開銷較大。
* 創建進程(一般是調用`fork`方法)的性能開銷較大。
~~~
**線程**:一個進程可以有多個線程,每個線程會共享父進程的資源(創建線程開銷占用比進程小很多,可創建的數量也會很多),有時被稱為輕量級進程(Lightwight Process,LWP),是操作系統調度(CPU調度)執行的最小單位。
**多線程比多進程之間更容易共享數據,在上下文切換中線程一般比進程更高效**。
#### 有多進程為什么還需要線程?
~~~
1、創建線程比創建進程要快 10 倍甚至更多
2、線程之間能夠非常方便、快速地共享數據
~~~
**協程**:用戶態的線程。通常創建協程時,會從進程的堆中分配一段內存作為協程的棧。
線程的棧有 8 MB,而協程棧的大小通常只有 KB,而 Go 語言的協程更夸張,只有 2-4KB,非常的輕巧。
#### 有多線程為什么需要協程
* 節省 CPU:避免系統內核級的線程頻繁切換,造成的 CPU 資源浪費。好鋼用在刀刃上。而協程是用戶態的線程,用戶可以自行控制協程的創建于銷毀,極大程度避免了系統級線程上下文切換造成的資源浪費。
* 節約內存:在 64 位的Linux中,一個線程需要分配 8MB 棧內存和 64MB 堆內存,系統內存的制約導致我們無法開啟更多線程實現高并發。而在協程編程模式下,可以輕松有十幾萬協程,這是線程無法比擬的。
* 穩定性:前面提到線程之間通過內存來共享數據,這也導致了一個問題,任何一個線程出錯時,進程中的所有線程都會跟著一起崩潰。
* 開發效率:使用協程在開發程序之中,可以很方便的將一些耗時的IO操作異步化,例如寫文件、耗時 IO 請求等。
## 進程、線程、協程的堆棧區別是什么?
* 進程:有獨立的堆棧,不共享堆也不共享棧;由操作系統調度;
* 線程:有獨立的棧,共享堆而不共享棧;由操作系統調度;
* 協程:有獨立的棧,共享堆而不共享棧;由程序員自己調度。
## goroutine泄露的問題
協程泄露:指goroutine創建后,長時間得不到釋放,并且還在不斷地創建新的goroutine協程,最終導致內存耗盡,程序崩潰。
1、只發送不接收
2、只接收不發送
3、只聲明了,沒有初始化
4、只加鎖,沒有解鎖
5、`wg.Add`的數量與`wg.Done`數量并不匹配,因此在調用`wg.Wait`方法后一直阻塞等待。
- Go準備工作
- 依賴管理
- Go基礎
- 1、變量和常量
- 2、基本數據類型
- 3、運算符
- 4、流程控制
- 5、數組
- 數組聲明和初始化
- 遍歷
- 數組是值類型
- 6、切片
- 定義
- slice其他內容
- 7、map
- 8、函數
- 函數基礎
- 函數進階
- 9、指針
- 10、結構體
- 類型別名和自定義類型
- 結構體
- 11、接口
- 12、反射
- 13、并發
- 14、網絡編程
- 15、單元測試
- Go常用庫/包
- Context
- time
- strings/strconv
- file
- http
- Go常用第三方包
- Go優化
- Go問題排查
- Go框架
- 基礎知識點的思考
- 面試題
- 八股文
- 操作系統
- 整理一份資料
- interface
- array
- slice
- map
- MUTEX
- RWMUTEX
- Channel
- waitGroup
- context
- reflect
- gc
- GMP和CSP
- Select
- Docker
- 基本命令
- dockerfile
- docker-compose
- rpc和grpc
- consul和etcd
- ETCD
- consul
- gin
- 一些小點
- 樹
- K8s
- ES
- pprof
- mycat
- nginx
- 整理后的面試題
- 基礎
- Map
- Chan
- GC
- GMP
- 并發
- 內存
- 算法
- docker