編譯時一個將源代碼翻譯成低級語言的過程。編譯過程比較慢,在設計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;
在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本身去處理。主線程在退出前不會等待全部的協程執行完畢,因此主線程在退出前,協程纔有機會執行,因此咱們必須讓代碼協同。
目標:可以處理從上百萬個端點發來的大量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()
併發編程的挑戰在於數據共享。若是你的go協程沒有共享數據,就不須要擔憂她們。可是現實場景中經常須要多個請求共享數據。通道用於go協程之間傳遞數據,go協程能夠經過通道,傳遞數據到另外一個go協程。結果就是任什麼時候候只有一個go協程能夠訪問數據。
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能夠確保往一個通道發送數據時,僅一個單獨的接收器能夠接收。通道提供了全部的同步代碼。