Golang筆記

靜態編譯

編譯時一個將源代碼翻譯成低級語言的過程。編譯過程比較慢,在設計Go時,編譯速度是主要的設計目標之一。靜態類型意味着變量必須指定一個類型,如整形,字符串,布爾,數組等,能夠在聲明變量時指定變量類型,大多數狀況下,讓編譯器自動去推斷變量類型。git

垃圾回收

變量有一個肯定的生命週期。例如函數中定義的局部變量,當函數退出時變量就不存在了。語言的垃圾回收機制能夠記錄不在使用的變量,而後釋放他們佔用的內存。垃圾回收機制帶來一些性能影響。github

代碼運行

go run命令會先編譯而後再運行你的代碼,會在一個臨時目錄下編譯這段代碼,而後執行,最後自動清除生成的臨時文件。若是隻是編譯代碼可使用go build。sql

變量賦值

第一種方式:編程

var power int
power = 9000

第二種方式:數組

var power int = 9000

第三種方式:用於聲明一個變量並給變量賦值,go能夠推斷變量類型,在第一次聲明變量時,使用:=,此時肯定了變量類型。但隨後對於此變量的賦值,使用=。緩存

power := 9000

gg := getPower()

func getPower() int{
    return 9001
}

第四種方式:go支持多個變量同事賦值安全

name, power := "Goku", 9000

函數聲明

函數支持多值返回架構

沒有返回值:併發

func log(message string){
}

一個返回值:app

func add (a int, b int) int{
}

兩個返回值: func power(name string)(int,bool){ } 多個返回值的場景使用比較多,若是隻想得到返回值中的某個值,能夠將另外一個返回賦值給_:

_, exists:=power("goku")

if exists == false{
}

_是一個空白標識符,多用在返回值時沒有真正的賦值,不管返回值是什麼類型。

若是函數的參數都是相同的類型,能夠簡潔的定義:

func add(a,b int) int{
}

結構體

go 不像面嚮對象語言,沒有對象和繼承的概念。所以也沒有不少面向對象的語言的特徵好比多態和重載。 go提供告終構體,如:

type Sanya struct{
    Name string
    Province int
}

經過簡單的方式建立一個結構體值類型:

goku := Sanya{
    Name : "sanya",
    Province :23,
}

注意上面結構體結尾的逗號是不能省的。 當不須要給結構體設置任何值甚至任何字段:

goku := Sanya{}

goku := Sanya{Name:"sanya"}
goku.Province = 23

也能夠省略字段的名字:

goku := Sanya{"sanya",23}

大多數狀況,咱們不但願變量直接關聯一個值,而是但願一個指針指向變量的值,由於在go語言中,函數的參數傳遞都是按拷貝傳遞。指針是一個內存地址。經過指針能夠找到這個變量的實際的值,是一種間接的取值。

func main(){
    goku := &Sanya{"sanya",9000}
    Super(goku)
    fm.Println(goku.Power)
}

func Super(s *Sanya){
    s.Power = 10000
}

結果是10000,這樣就是傳遞了指針。複製一個指針變量的開銷比複製一個複製複雜的結構體小。

構造函數

結構體沒有構造函數,你能夠建立一個函數返回一個相應類型的實例來代替:

func NewSanya(name string, province int) Sanya{
    return Sanya{
        Name:name,
        Province:province,
    }
}

爲新建立的對象分配內存:

goku := &Sanya{
    name:"goku",
    province:23
}

對已定義對結構體進行擴展:

type Sanya struct{
    Name string
    Province int
    Father *Sanya
}

初始化:

gohan := &Sanya{
    Name:"Sanya",
    Province:23,
    Father:&Sanya{
        Name:"Haiko",
        Province:23,
        Father:nil,
    }
}

指針類型和值類型

當你寫go代碼時,很天然就會問本身,這裏應該使用值類型仍是指針類型。若是你不肯定時,就使用指針。值傳遞是一種確保數據不可變對方法。有時候須要函數內對調用代碼進行改變,須要使用指針。 即便你不打算改變數據,也要考慮大結構體拷貝的開銷,若是小的結構體能夠進行拷貝。

數組

數組是固定大小的。聲明數組時必須指定他們的大小,一旦數組大小被指定,他就不能擴展變大。

var scores [10]int
scores[0] = 300

// 直接初始化一個有值的數組
scores := [4]int{9001,9002,9003,9004}

// 遍歷數組
for index,value:= range scores{
}

數組效率高可是不靈活,咱們處理數據時,通常不知道元素的數量,所以使用切片。

切片

在go中你通常不多使用數組。會更多使用切片。切片是一個輕量級的結構體封裝,這個結構體被封裝後,表明一個數組的一部分。 建立切片時和建立數組不一樣的是,不須要指定大小。

scores := []int{1,2,3,4}

scores := make([]int,0,10) //長度爲0可是容量爲10的分片
scores := append(scores,5)

哈希表

定義鍵值對,能夠經過make建立:

lookup := make(map[stirng]int)
lookup["goku"] = 9001

包管理

若是你已經裝來git,執行以下命令:

go get github.com/mattn/go-sqlite3 go get將獲得這些遠程文件並將他們保存在你的工做空間。導入包到工做空間:

import(
    "github.com/mattn/go-sqlite3"
)

接口

接口是一種類型,他只定義了聲明,沒有具體實現。如:

type Logger interface{
    Log(message string)
}

接口能夠在代碼中實現解耦。

Go中Buffer高效拼接字符串及自定義線程安全Buffer:

Go中可使用「+」合併字符串,但這種方式效率很是低,每合併一次,都建立一個新的字符串,就必須遍歷複製一次字符串。能夠經過Buffer高效拼接字符串。 使用bytes.Buffer來組裝字符串,不須要複製,只須要將添加字符串放在緩存末尾便可。因爲Buffer中的write和read函數中都未發現鎖的蹤跡,因此Buffer的並不是是不安全的。

Go特有的併發編程模型方式:

Goroutine & Channel;

協程Goroutine

在Go世界裏,每個併發執行的活動稱爲goroutine。 經過goroutine,能夠實現並行運算,十分便捷。 go協程相似於一個線程,可是協程由go自身調度,不是系統。在協程中對代碼能夠和其餘代碼併發執行。

func main(){
    fmt.Println("start")
    go process()
    time.Sleep(time.Millisecond * 10)
    fmt.Println("done")
}

func process(){
    fmt.Println("processing")
}

咱們如何啓動一個協程對。只是簡單對將go關鍵字附在要執行對函數前面便可。 go協程很容易建立且開銷極小。最終多個go協程將會在同一個底層系統線程上運行。這也是常稱爲M:N線程模型,由於咱們有M個應用協程運行在N個系統線程上。結果就是,一個go協程對開銷和系統線程比起來相對低(通常都是幾十K)。在現代硬件上,能夠跑成千上萬對協程。 還隱藏了映射和調度的複雜性。併發執行讓go本身去處理。主線程在退出前不會等待全部的協程執行完畢,因此主線程在退出前,協程纔有機會執行,因此咱們必須讓代碼協同。

Go高併發Http請求

目標:可以處理從上百萬個端點發來的大量POST請求。HTTP請求處理函數會收到包含不少payloads的JSON文檔。這些payloads須要被寫到Amazon S3上,接着有map-reduce系統來處理。

咱們一般會將請求放入隊列,經過必定數量(例如經過核心CPU數)goroutine組成一個worker pool,worker pool中的worker讀取隊列執行任務,最理想的狀況下,CPU的全部核都會並行的執行任務。

而後設置兩個集羣,一個用做處理HTTP請求,一個用做workers。這樣能夠根據處理後臺的工做量進行擴容。

主Goroutine作了什麼?

  • 啓動系統檢測器;

  • 設定通用配置,檢查運行環境;

  • 建立定時垃圾回收器;

  • 執行main包的init函數;

  • 執行main包的main函數;

  • 進行一些善後處理工做;

同步

建立一個協程沒有難度,啓動不少協程開銷也不大。可是併發執行的代碼須要協同。爲了解決這個問題,go提供了管道(channels)。 協程會將代碼函數拆分爲不少彙編指令,在併發場景下,若是想安全的操做一個變量,惟一的手段就是讀取該變量。能夠任意多的讀,但寫必須同步。能夠依賴於cpu架構的真正原子操做。更多時候使用一個互斥鎖。

//定義鎖
lock sync.Mutex

//使用鎖
lock.Lock()

//開鎖
defer lock.Unlock()

Channel

併發編程的挑戰在於數據共享。若是你的go協程沒有共享數據,就不須要擔憂她們。可是現實場景中經常須要多個請求共享數據。通道用於go協程之間傳遞數據,go協程能夠經過通道,傳遞數據到另外一個go協程。結果就是任什麼時候候只有一個go協程能夠訪問數據。

  • 即通道類型,Go的預約義類型之一。
  • 類型化,併發安全的通用型管道。
  • 用於在多個Goroutine之間傳遞數據。
  • 以通信的方式共享內存的最直接體現。

Channel的Happens before原則:

發送操做開始->值拷貝(產生副本)->發送操做結束->接收操做開始->接收方持有值->接收操做結束。 Channel能夠協調多個Goroutine的運行。

通道也有類型,就是將要在通道傳遞到數據的類型,如建立一個通道,這個通道能夠用來傳遞一個整數:

c := make(chan int)

// 將這個通道傳遞給一個函數
fun worker(c chan int){
}

//通道發送數據
CHANNEL <- DATA

//通道接收數據
VAR := <-CHANNEL

尖頭指向的方向是數據的流動方向。 當咱們從一個通道接收或向通道發送數據時會阻塞,直到有數據。

定義一個數據處理者結構體:

type Worker struct{
    id int
}

fun (w Worker) process(c chan int){
    for{
        data := <-c
        fat.Pringtf("worker data",w.id)
    }
}

咱們的worker很簡單,會一直等待數據,直到數據可用,而後處理它,他在一個循環中,永遠盡職的等待更多的數據並處理。

啓動多個worker:

c := make(chan int)
for i:=0; I<4; I++{
    worker := Worker{id:i}
    go worker.process(c)
}

建立一些任務:

for{
    c <- rand.Int()
    time.Sleep(time.Millisecond*50)
}

咱們不知道哪一個worker將得到數據。但go能夠確保往一個通道發送數據時,僅一個單獨的接收器能夠接收。通道提供了全部的同步代碼。

相關文章
相關標籤/搜索