本篇是語言討論的「傳統專案」。每個寫go語言討論的人,都會介紹它的發展歷程,應用領域,優缺點和特點來介紹go語言的那點事,當然這點事只是我從我的視角來看的。當然如果你有自己對go語言的看法,那就更好了,歡迎討論!
go語言由google公司開源的用於提高程式設計師程式設計效率的程式語言。它是一門簡單易學,靜態編譯,原生並行以及向後相容性的高效快捷的輕量級語言。
目前go語言每半年會迭代一個新版本,從2012年的go1.0開始,已經歷經21個版本,目前已經到了go1.20,從2015年開始進入快速發展期,以用於大型專案的工程語言進入人們的視野。
go語言在go1.1-g1.4時還不具備工程化的條件,但在go1.5時開始具備工程化的條件,這是因為(1)在go1.5之前的版本golang採用的是c語言編譯器,(2)gc的STW時間會很長,(3)第三方包沒有合理的存放位置。
而在go1.5版本開始實現go語言自舉,在這個版本里開始採用三色標記法,這使得golang的gc時間大幅下降,經過go1.6-go1.7的改進,使得golang的STW時間大幅縮短,另外在這個版本開始實驗vender機制。
這三個特性的加入使得golang在2015年之後開始火爆的原因,在go1.7正式使用vender機制,在go1.9版本STW時間縮短至100us以內,為此golang在go1.9版本已經完全具備工程化的條件。
之後golang在go1.11版本開始實驗go mod機制以替換vender機制,在go1.13正式引入go mod機制,徹底解決了第三方庫的版本問題,在go1.18版本中實驗golang的泛型特性,而在go1.20版本中正式引入golang的泛型特性。
go語言用於雲原生開發,命令列介面,網站開發,運維開發領域以及儲存開發。注意我這裡介紹go語言的應用場景來自go語言官網實際上,你會發現go語言還能應用於其他領域,如數位貨幣(以太坊),物聯網等等(因為我不熟悉這些領域,所以我可以假裝不知道嗎?)
隨著docker和kubernetes等殺手級應用的出現和發展,golang逐漸成為雲和網路服務領域中舉足輕重的語言,目前由超過75%的雲原生計算基金專案都是使用go語言開發的。
對於命令列介面應用,go語言能夠將其快速地構建成二進位制程式,提供跨平臺工作的開發方式以及強大的社群支援。我們可以在windows或者mac上開發或者偵錯,在Linux上編譯部署。它通過靜態編譯構建的二進位制程式隨插即用,幾乎不需要任何其他依賴。
例如,hub是github推出的用於輔助git的命令列工具,它是用go語言編寫的。另外如greenplum的gpbackup和gprestore也是用go語言編寫的。
go語言不僅天生支援高並行,能以極小的開銷約4K的記憶體啟動一個攜程goroutine,而且go語言的標準庫對於web開發的強大支援,能讓業務人員專注於業務開發,這些使得go語言在網站開發方面佔有一席之地。
注意go語言其實已經有gin以及beego這樣成熟的web框架,甚至有人已經將gin,vue3結合起來gin-vue-admin這樣的專案。我的建議是如果你寫的web服務很簡單,那麼go語言標準庫基本已經夠用,否則請選擇上述框架。
go語言在運維開發編寫指令碼時擁有大量優秀的標準庫支援。另外,go語言還提供了豐富的工具鏈幫助用於編寫高效、健壯以及可維護性強的程式。
目前在運維開發領域,使用go語言開發優秀的專案有類似於zabbix的Prometheus,將運維資料視覺化的grafana,用於容器CI/CD的Docker CI/CD等等
go語言是帶有gc,高效能並且高並行的特點,為此在儲存領域也有比較好的發展。例如分散式資料庫tidb(sql層使用go語言開發)、時序性資料庫influxdb、分散式kv儲存etcd以及分散式訊息佇列nsq都是其中的佼佼者。
sum := 0
for i := 0; i < 30; i ++ {
if i % 2 == 0 {
sum += i
}
}
fmt.Println(sum)
go語言許多語法和C語言很相似,學習難度和python類似,學習成本較低,大約1-2周初學者就能開發一些實用的小程式。當然如果你學過C語言,你將更快入門go語言
go語言採用靜態編譯的方式,產生出的二進位制程式隨插即用,幾乎無需其他依賴。
c := make(chan int)
go func() {
for {
select {
case e, ok := <-c:
if !ok {
return
}
fmt.Println(e)
}
}
}()
a := []int{5,2,4,3,1}
for _, v := range a {
c <- v
}
close(c)
go語言可以很容易地實現並行程式設計,通過保留字go即可實現,使用chan實現攜程間通訊,你可以認為chan是一個加鎖的佇列或者訊息管道。
如上,程式碼通過保留字go呼叫匿名函數啟動了一個攜程,並且通過chan向這個攜程傳值。
注意:chan的傳入並非是全域性變數而是因為go語言匿名函數的閉包特性。
go語言雖然歷經時間不多,但是它的社群支援極為強大,如上go語言目前支援常用資料庫的存取。
使用go語言編寫的程式碼會被其之後版本的go語言相容,例如你用go語言1.8寫了程式碼,那麼幾乎不用做出任何更改在go語言1.17中直接編譯使用。這就意味著通過這個承諾,你幾乎無需修改程式碼就能讓go語言程式碼享受新版本帶來的效能提升。
go語言不僅有著c/c++,java等傳統編譯語言無法企及的編譯速度,又有類似於c/c++的執行速度,這些讓程式設計師有更好的程式設計體驗。當然據說這是Google公司的陰謀,讓程式設計師有「更多時間工作」。
這是相較於c/c++ ,java,javascript以及python等主流語言不一樣的地方,不算是優點也不算是缺點的語言特性
由於基本上目前的主流語言的函數都是單返回值,而golang為了使錯誤能夠被返回,採用了含有多返回值的函數,如下:
func Write(data []byte) (n int, err error)
還有表示式中也可以使用多返回值,例如交換a,b可以寫成
a:= 1
b:= 2
a,b = b,a
還有前面提到的
for _, v := range a {
c <- v
}
以及
select {
case e, ok := <-c:
if !ok {
return
}
fmt.Println(e)
}
golang並非通過指定關鍵字private和public去實現物件導向的封裝特性,而是通過大小寫去指定成員是否公有和私有,在下述程式碼中,writer包外的只能存取公有的成員變數,函數,或者方法。
package writer
import (
"os"
)
var (
OsType = "Linux" //公有變數
osType = "linux" //私有變數
)
type FileWriter struct {
Filename string //共有成員變數
f * os.file //私有成員變數
}
//公有函數
func NewFileWriter(filename string) (*FileWriter, error){
return newFileWriter(filename)
}
//私有函數
func newFileWriter(filename string) (fw *FileWriter, err error){
fw = &FileWriter{
Filename:filename,
}
err = fw.createFile()
return
}
//公有方法
func (w *FileWriter) Write(data []byte) (n int, err error){
return w.f.Write(data)
}
//公有方法
func (w *FileWriter) Close() (err error){
return w.f.Close()
}
//私有方法
func (w *FileWriter) createFile() (err error){
w.f, err = os.Create(w.Filename)
if err != nil{
return err
}
return
}
golang由於採用了靜態編譯的方式,使得golang無法在執行時載入,為此它使用組合而沒有繼承,使用面向介面來實現物件導向的多型特性,這樣更容易寫出高內聚低耦合的程式碼。
例如我們定義一個介面
type Writer interface {
Write(data []byte) (n int, err error)
}
然後上述程式碼中的FileWriter已經實現該介面
type FileWriter struct {
filename string
f * os.file
}
func (w *FileWriter) Write(data []byte) (n int, err error){
return w.f.Write(data)
}
網路程式設計往往如下圖採用非同步回撥的方式去完成事件處理。
但是在golang中網路程式設計可以使用同步模式去完成,這可能不是一個很好的例子。
func HandleConn(conn net.Conn) {
defer conn.Close()
for {
buf := make([]byte, 1024)
cnt, err := conn.Read(buf)
if err != nil {
//請處理錯誤
}
//處理資訊
}
}
func main() {
listen, err := net.Listen("tcp", ":8888")
if err != nil {
//處理錯誤
return
}
for {
conn, err := listen.Accept()
if err != nil {
//處理錯誤
return
}
go HandleConn(conn)
}
}