1. log(github.com/sirupsen/logrus)
log.Fatal
會直接中斷當(dāng)前服務(wù), 即使是用 go func(){log.Fatal("end")}()
也會中斷整個服務(wù)
2. goroutine
并發(fā)線程有3種模型(用戶空間 - 系統(tǒng)空間):
- 內(nèi)核級線程模型 1(系統(tǒng)線程-輕量級線程):1(用戶線程) context switch 性能低
- 用戶級線程模型 1(系統(tǒng)線程-輕量級線程):N(用戶線程) context switch 成本低(實際由于無法有效利用多處理器, 效率低)
-
混合型線程模型 N(系統(tǒng)線程-輕量級線程):M(用戶線程) golang的
goroutine(coroutine)
語義實現(xiàn)了此模型
3. channel
FIFO(管道先進(jìn)先出)
for v := range c {}
這段代碼, 即使c已經(jīng)沒有數(shù)據(jù)進(jìn)入, 也不會中斷, 只有當(dāng)使用close(c)
主動關(guān)閉 channel, 才會中斷這個死循環(huán)
for { select {case data, flag := <- m.c} }
這段代碼主動close(c)
, 會讓返回的flag=false
, 為了避免死循環(huán), 應(yīng)該主動的作出動作. 而整個for循環(huán)要退出, 最好做一個退出的event channel作為monitor,select
本身隨機(jī)的從多個case
里面挑選一個執(zhí)行
q := make(chan string)
默認(rèn)只有0個空間的chan(串行)
如果不啟動一個go func(){}(<-q)
去消費(fèi), 直接使用q<-"s"
, 會導(dǎo)致進(jìn)程被block(類似于沒有打開任何通道, 沒有辦法進(jìn)入數(shù)據(jù)), 編譯器有時能檢測到?jīng)]有任何的goroutine, 導(dǎo)致deadlock
此種類型保證數(shù)據(jù)被消費(fèi)后, 再繼續(xù)執(zhí)行
q := make(chan string, 1)
含有1個空間的chan(并行, 并非并行度越大越好)
這時允許有一個buffer的空間不導(dǎo)致block
此方法允許存在一定數(shù)據(jù)buffer
沒有 q<-"s"
直接使用 <-q
也會 block, 因此建議使用 go func(){}()
實現(xiàn)協(xié)同編程
4. init()
一個Package
下是不可以有重複的變數(shù)或者是函式名稱
init()
可以在同一個 package 內(nèi)宣定義多次
init()
執(zhí)行的時機(jī)會是在執(zhí)行 main func
之前
5. 指針變量
*demo.Name = "ss"
是會直接修改指針指向?qū)ο蟮闹?/p>
// 騷氣的傳遞
func (s *ShardWorker) callStub(request interface{}) (interface{}, error) {
return s.backend.sendGeneralRequest(request)
}
func (s *ShardWorker) PingNode(request *index_rpc.PingNodeRequest) (*index_rpc.PingNodeResponse, error) {
v, err := s.callStub(request)
if err != nil {
return nil, err
}
return v.(*index_rpc.PingNodeResponse), nil
}
6. timer(定時器)/ticker(斷路器)
定時器(定時n后觸發(fā)一次)
//初始化定時器
t := time.NewTimer(2 * time.Second)
//當(dāng)前時間
now := time.Now()
fmt.Printf("Now time : %v.\n", now)
expire := <- t.C // t.C 阻塞監(jiān)聽定時器事件, 只會觸發(fā)一次
fmt.Printf("Expiration time: %v.\n", expire)
斷路器(每隔n后觸發(fā)一次)
//初始化斷續(xù)器,間隔2s
var ticker *time.Ticker = time.NewTicker(1 * time.Second)
go func() {
// ticker.C 阻塞監(jiān)聽定時器事件, 隔一段時間觸發(fā)一次
for t := range ticker.C {
fmt.Println("Tick at", t)
}
}()
time.Sleep(time.Second * 5) //阻塞,則執(zhí)行次數(shù)為sleep的休眠時間/ticker的時間
ticker.Stop()
fmt.Println("Ticker stopped")
7. runtime.LockOSThread()
golang的scheduler可以理解為公平協(xié)作調(diào)度和搶占的綜合體,他不支持優(yōu)先級調(diào)度棉饶。當(dāng)你開了幾十萬個goroutine,并且大多數(shù)協(xié)程已經(jīng)在runq等待調(diào)度了, 那么如果你有一個重要的周期性的協(xié)程需要優(yōu)先執(zhí)行該怎么辦栏尚?
使用runtime.LockOSThread()
該方法的作用是可以讓當(dāng)前協(xié)程綁定并獨立一個線程M, 子協(xié)程不會繼承lockOSThread特性
可以借助runtime.LockOSThread()
方法來綁定線程滑绒,綁定線程M后的好處在于钦铁,他可以由system kernel內(nèi)核來調(diào)度,因為他本質(zhì)是線程了
tid: 當(dāng)使用 goroutine, tid會新建. 當(dāng)不使用 goroutine, tid會繼承
pid: 使用/不使用 goroutine, pid會繼承
ppid: 使用/不使用 goroutine, ppid會繼承
8. defer
defer
類似于 java 中的finally
, 在一個 func block 結(jié)束之前, 會自動執(zhí)行
如果一個func內(nèi)部有多個defer, defer是按照Stack
進(jìn)行存儲的
// 打印是 s1 close s1 v2 close s1 v1
func s1() {
print("s1\t")
defer print("close s1 v1\t")
defer println("close s1 v2")
}
8. Mutex & RWMutex
讀多寫少的情況下, 盡可能的使用
sync.RWMutex
, 在 read 操作的時候, 使用RLock()
, 在 set 操作的時候, 使用Lock()
不要在一個
for { select mu.Lock() defer mu.Unlock()}
, 因為這本身是一個func, 在func結(jié)束前是不會釋放鎖的
mutex 不具備 goroutine 的重入特性
8.1 Mutex
使用 Mutex.Lock
會使所有的協(xié)程等待這個 mutext 的釋放, 實現(xiàn)并發(fā)編程(非常的重, 此方法會導(dǎo)致用戶空間->系統(tǒng)空間的context switch
, 性能很差)
- Mutex 為互斥鎖才漆,Lock() 加鎖牛曹,Unlock() 解鎖
- 在一個 goroutine 獲得 Mutex 后,其他 goroutine 只能等到這個goroutine 釋放該 Mutex
- 使用 Lock() 加鎖后醇滥,不能再繼續(xù)對其加鎖黎比,直到利用 Unlock() 解鎖后才能再加鎖
- 在 Lock() 之前使用 Unlock() 會導(dǎo)致 panic 異常
- 已經(jīng)鎖定的 Mutex 并不與特定的 goroutine 相關(guān)聯(lián),這樣可以利用一個 goroutine 對其加鎖鸳玩,再利用其他 goroutine 對其解鎖
- 在同一個 goroutine 中的 Mutex 解鎖之前再次進(jìn)行加鎖阅虫,會導(dǎo)致死鎖
- 適用于讀寫不確定,并且只有一個讀或者寫的場景
// 不需要主動顯示的 new一個對象, 只需要保存變量即可
var mu sync.Mutext
func hi() {
mu.Lock()
defer mu.Lock()
}
8.2 RWMutex
- RWMutex 是單寫多讀鎖不跟,該鎖可以加多個讀鎖或者一個寫鎖
- 讀鎖占用的情況下會阻止寫颓帝,不會阻止讀,多個 goroutine 可以同時獲取讀鎖
- 寫鎖會阻止其他 goroutine(無論讀和寫)進(jìn)來窝革,整個鎖由該 goroutine 獨占
- 適用于讀多寫少的場景
8.2.1 Lock() 和 Unlock()
- Lock() 加寫鎖购城,Unlock() 解寫鎖
- 如果在加寫鎖之前已經(jīng)有其他的讀鎖和寫鎖,則 Lock() 會阻塞直到該鎖可用聊闯,為- 確保該鎖可用工猜,已經(jīng)阻塞的 Lock() 調(diào)用會從獲得的鎖中排除新的讀取器,即寫鎖權(quán)限高于讀鎖菱蔬,有寫鎖時優(yōu)先進(jìn)行寫鎖定
- 在 Lock() 之前使用 Unlock() 會導(dǎo)致 panic 異常
8.2.2 RLock() 和 RUnlock()
- RLock() 加讀鎖,RUnlock() 解讀鎖
- RLock() 加讀鎖時,如果存在寫鎖拴泌,則無法加讀鎖魏身;當(dāng)只有讀鎖或者沒有鎖時,可以加讀鎖蚪腐,讀鎖可以加載多個
- RUnlock() 解讀鎖箭昵,RUnlock() 撤銷單詞 RLock() 調(diào)用,對于其他同時存在的讀鎖則沒有效果
- 在沒有讀鎖的情況下調(diào)用 RUnlock() 會導(dǎo)致 panic 錯誤
- RUnlock() 的個數(shù)不得多于 RLock()回季,否則會導(dǎo)致 panic 錯誤
9. WaitGroup
這個demo每隔一秒, 會輸出一個數(shù)字, 所有數(shù)字輸出完成后, 結(jié)束集成
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(idx int) {
d := time.Duration(idx)
time.Sleep(d * time.Second)
println(idx)
wg.Done()
}(i)
}
wg.Wait()
wg.add
代表有增加n個正在處理的值, 當(dāng)wg.wait
時發(fā)現(xiàn)值不為0, 就會wait住, 當(dāng)wg.done
的時候,會對原子計數(shù)器做-1操作(有點類似Java中的CountDownLatch
或者CyclicBarrier
)
10. unsafe.Pointer
Go語言是個強(qiáng)類型語言家制。也就是說Go對類型要求嚴(yán)格,不同類型不能進(jìn)行賦值操作泡一。指針也是具有明確類型的對象颤殴,進(jìn)行嚴(yán)格類型檢查
str := C.CString("xxx")
沒有進(jìn)行內(nèi)存釋放, 需要在后面跟上 defer C.free(unsafe.Pointer(str))
主動釋放內(nèi)存
unsafe.Pointer
它表示任意類型且可尋址的指針值,可以在不同的指針類型之間進(jìn)行轉(zhuǎn)換(類似 C 語言的 void * 的用途)
uintptr(unsafe.Pointer(z))
變量表示Pointer對象所處的內(nèi)存地址
- 任何類型的指針值都可以轉(zhuǎn)換為 Pointer
- Pointer 可以轉(zhuǎn)換為任何類型的指針值
-
uintptr
可以轉(zhuǎn)換為Pointer
-
Pointer
可以轉(zhuǎn)換為uintptr
u := uint32(32)
i := int32(1)
fmt.Println(&u, &i) // 打印出地址
p := &i // p 的類型是 *int32
p = &u // 不能賦值
p = (*int32)(&u) // 強(qiáng)轉(zhuǎn)也不能
p = (*int32)(unsafe.Pointer(&u)) // 可以賦值
11. iota
常量計數(shù)器, 只能在常量的表達(dá)式中使用
// 每次 const 出現(xiàn)時鼻忠,都會讓 iota 初始化為0
const (
OutMute AudioOutput = iota // 0
OutMono // 1
OutStereo // 2
_
_
OutSurround // 5
)