Golang 基礎

1. log(github.com/sirupsen/logrus)

log.Fatal 會直接中斷當前服務, 即使是用 go func(){log.Fatal("end")}() 也會中斷整個服務

2. goroutine

并發(fā)線程有3種模型(用戶空間 - 系統(tǒng)空間):

  1. 內核級線程模型 1(系統(tǒng)線程-輕量級線程):1(用戶線程) context switch 性能低
  2. 用戶級線程模型 1(系統(tǒng)線程-輕量級線程):N(用戶線程) context switch 成本低(實際由于無法有效利用多處理器, 效率低)
  3. 混合型線程模型 N(系統(tǒng)線程-輕量級線程):M(用戶線程) golang的goroutine(coroutine)語義實現了此模型

3. channel

FIFO(管道先進先出)

for v := range c {} 這段代碼, 即使c已經沒有數據進入, 也不會中斷, 只有當使用 close(c) 主動關閉 channel, 才會中斷這個死循環(huán)

for { select {case data, flag := <- m.c} } 這段代碼主動close(c), 會讓返回的flag=false, 為了避免死循環(huán), 應該主動的作出動作. 而整個for循環(huán)要退出, 最好做一個退出的event channel作為monitor, select本身隨機的從多個case里面挑選一個執(zhí)行

q := make(chan string) 默認只有0個空間的chan(串行)

如果不啟動一個go func(){}(<-q)去消費, 直接使用q<-"s", 會導致進程被block(類似于沒有打開任何通道, 沒有辦法進入數據), 編譯器有時能檢測到沒有任何的goroutine, 導致deadlock

此種類型保證數據被消費后, 再繼續(xù)執(zhí)行

q := make(chan string, 1) 含有1個空間的chan(并行, 并非并行度越大越好)

這時允許有一個buffer的空間不導致block

此方法允許存在一定數據buffer

沒有 q<-"s" 直接使用 <-q 也會 block, 因此建議使用 go func(){}()實現協(xié)同編程

4. init()

一個Package下是不可以有重複的變數或者是函式名稱

init() 可以在同一個 package 內宣定義多次

init() 執(zhí)行的時機會是在執(zhí)行 main func 之前

5. 指針變量

*demo.Name = "ss" 是會直接修改指針指向對象的值

// 騷氣的傳遞
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)
//當前時間
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í)行次數為sleep的休眠時間/ticker的時間
ticker.Stop()
fmt.Println("Ticker stopped")

7. runtime.LockOSThread()

golang的scheduler可以理解為公平協(xié)作調度和搶占的綜合體,他不支持優(yōu)先級調度。當你開了幾十萬個goroutine,并且大多數協(xié)程已經在runq等待調度了, 那么如果你有一個重要的周期性的協(xié)程需要優(yōu)先執(zhí)行該怎么辦?

使用runtime.LockOSThread() 該方法的作用是可以讓當前協(xié)程綁定并獨立一個線程M, 子協(xié)程不會繼承lockOSThread特性

可以借助runtime.LockOSThread()方法來綁定線程,綁定線程M后的好處在于,他可以由system kernel內核來調度,因為他本質是線程

tid: 當使用 goroutine, tid會新建. 當不使用 goroutine, tid會繼承
pid: 使用/不使用 goroutine, pid會繼承
ppid: 使用/不使用 goroutine, ppid會繼承

8. defer

defer 類似于 java 中的finally, 在一個 func block 結束之前, 會自動執(zhí)行

如果一個func內部有多個defer, defer是按照Stack進行存儲的

// 打印是 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結束前是不會釋放鎖的

mutex 不具備 goroutine 的重入特性

8.1 Mutex

使用 Mutex.Lock 會使所有的協(xié)程等待這個 mutext 的釋放, 實現并發(fā)編程(非常的重, 此方法會導致用戶空間->系統(tǒng)空間context switch, 性能很差)

  • Mutex 為互斥鎖,Lock() 加鎖,Unlock() 解鎖
  • 在一個 goroutine 獲得 Mutex 后,其他 goroutine 只能等到這個goroutine 釋放該 Mutex
  • 使用 Lock() 加鎖后,不能再繼續(xù)對其加鎖,直到利用 Unlock() 解鎖后才能再加鎖
  • 在 Lock() 之前使用 Unlock() 會導致 panic 異常
  • 已經鎖定的 Mutex 并不與特定的 goroutine 相關聯,這樣可以利用一個 goroutine 對其加鎖,再利用其他 goroutine 對其解鎖
  • 在同一個 goroutine 中的 Mutex 解鎖之前再次進行加鎖,會導致死鎖
  • 適用于讀寫不確定,并且只有一個讀或者寫場景
// 不需要主動顯示的 new一個對象, 只需要保存變量即可
var mu sync.Mutext
func hi() {
    mu.Lock()
    defer mu.Lock()
}

8.2 RWMutex

  • RWMutex 是單寫多讀鎖,該鎖可以加多個讀鎖或者一個寫鎖
  • 讀鎖占用的情況下會阻止寫,不會阻止讀,多個 goroutine 可以同時獲取讀鎖
  • 寫鎖會阻止其他 goroutine(無論讀和寫)進來,整個鎖由該 goroutine 獨占
  • 適用于讀多寫少場景

8.2.1 Lock() 和 Unlock()

  • Lock() 加寫鎖,Unlock() 解寫鎖
  • 如果在加寫鎖之前已經有其他的讀鎖和寫鎖,則 Lock() 會阻塞直到該鎖可用,為- 確保該鎖可用,已經阻塞的 Lock() 調用會從獲得的鎖中排除新的讀取器,即寫鎖權限高于讀鎖,有寫鎖時優(yōu)先進行寫鎖定
  • 在 Lock() 之前使用 Unlock() 會導致 panic 異常

8.2.2 RLock() 和 RUnlock()

  • RLock() 加讀鎖,RUnlock() 解讀鎖
  • RLock() 加讀鎖時,如果存在寫鎖,則無法加讀鎖;當只有讀鎖或者沒有鎖時,可以加讀鎖,讀鎖可以加載多個
  • RUnlock() 解讀鎖,RUnlock() 撤銷單詞 RLock() 調用,對于其他同時存在的讀鎖則沒有效果
  • 在沒有讀鎖的情況下調用 RUnlock() 會導致 panic 錯誤
  • RUnlock() 的個數不得多于 RLock(),否則會導致 panic 錯誤

9. WaitGroup

這個demo每隔一秒, 會輸出一個數字, 所有數字輸出完成后, 結束集成

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個正在處理的值, 當wg.wait時發(fā)現值不為0, 就會wait住, 當wg.done的時候,會對原子計數器做-1操作(有點類似Java中的CountDownLatch或者CyclicBarrier)

10. unsafe.Pointer

golang 內存對齊優(yōu)化

Go語言是個強類型語言。也就是說Go對類型要求嚴格,不同類型不能進行賦值操作。指針也是具有明確類型的對象,進行嚴格類型檢查

str := C.CString("xxx") 沒有進行內存釋放, 需要在后面跟上 defer C.free(unsafe.Pointer(str)) 主動釋放內存

unsafe.Pointer它表示任意類型且可尋址的指針值,可以在不同的指針類型之間進行轉換(類似 C 語言的 void * 的用途)

uintptr(unsafe.Pointer(z))變量表示Pointer對象所處的內存地址

  • 任何類型的指針值都可以轉換為 Pointer
  • Pointer 可以轉換為任何類型的指針值
  • uintptr 可以轉換為 Pointer
  • Pointer 可以轉換為 uintptr
u := uint32(32)
i := int32(1)
fmt.Println(&u, &i) // 打印出地址
p := &i // p 的類型是 *int32
p = &u // 不能賦值
p = (*int32)(&u) // 強轉也不能
p = (*int32)(unsafe.Pointer(&u)) // 可以賦值

11. iota

常量計數器, 只能在常量的表達式中使用

// 每次 const 出現時,都會讓 iota 初始化為0
const (
    OutMute AudioOutput = iota // 0
    OutMono                    // 1
    OutStereo                  // 2
    _
    _
    OutSurround                // 5
)
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 在 go 語言中提供了并發(fā)編程模型和工具外,還提供傳統(tǒng)的同步工具鎖,包括互斥鎖和讀寫鎖有關互斥鎖有些內容我們必須清...
    zidea閱讀 682評論 0 10
  • 一、各種語言入門例子:打印Hello,World! package main import ( "fmt" ) f...
    Boger_8cf1閱讀 154評論 0 0
  • 我的媽媽,童年時沒有完整的家,所以她加倍愛我;她經歷了知青下鄉(xiāng),所以分外熱愛工作。她熱情、聰敏、美貌、慈愛...
    洪霞_27cd閱讀 263評論 0 2
  • 今天學校又被淹了 哪兒都去不了 所以這是今天的推送hhhhhhhh
    章魚薇閱讀 184評論 0 0
  • 其實有不詳的預感早就告訴我。
    馬上做閱讀 102評論 0 0

友情鏈接更多精彩內容