golang 入門初體驗

Post on 16-Jan-2017

112 Views

Category:

Education

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Golang 入門初體驗

學習主題• 社群成立目的簡介• 自我介紹與哈啦• 功力複習• 程式實作 (Golang)

• 問題與交流

個人簡介諸葛魔斌工作室:諸葛魔斌科技創作室學歷:高應大 --- 工管系 ( 非資訊科系 )E-Mail : chugermobin@gmail.com官方網站: http://twcts.comFacebook : https://goo.gl/volM7Z粉絲團 : https://goo.gl/LI08wh社群 : https://goo.gl/kjCsz1Line (id) : http://goo.gl/ayGW7dLine@ : http://goo.gl/pQHjWHLine Bot : http://goo.gl/ljdxfVPlay 商店: http://goo.gl/CkVdsdApp Store : http://apple.co/1Qehd64專長: VB(VBA) 、 C# 、 Java(Android) 、 Swift(iOS) 、 PHP 、 Python 、 Conrona 、 Golang 、 Unity 、 MySQL 、 FreeBSD 、 Server 架設、 CAD 、 ……

楊政斌

學習程式的歷程學生時期:打電動,沒有日夜的界限二專畢業:初次接觸 dBase 、 Lotus 使用 Clipper 做經銷存、應收付帳系統

工作階段 1 :接觸 Office ,開始使用 Excel(Lotus)

工作階段 2 :品保工作,自學圖表、函數、 VBA ,做出自動化品管系統興趣展開:自學 VB ,製作 ERP 系統,接觸 Linux 、 FreeBSD ,自學架設 Server

諸葛魔斌誕生

大學時期:半自學 Android App(Java) 、 C# 、 iOS App(Swift)

工作轉變:成立諸葛魔斌科技創作室,接案學習,救國團電腦資訊講師實績:

Golang 入門初體驗Go ,又稱 golang ,是 Google 開發的一種靜態強型別、編譯型,並發型,並具有垃圾回收功能的程式語言。羅伯特 · 格瑞史莫,羅勃 · 派克( Rob Pike )及肯 · 湯普遜於 2007 年 9 月開始設計 Go 語言,稍後 Ian Lance Taylor, Russ Cox 加入專案中。 Go 語言是基於 Inferno 作業系統所開發的。 Go 語言於 2009 年 11 月正式宣布推出,成為開放原始碼專案,並在 Linux 及 Mac OS X 平台上進行了實現,後追加 Windows 系統下的實作。Go 語言的語法接近 C 語言,但是對於變數的聲明是不同的,其他語法不同之處是 For 迴圈和 if 判斷語句不需要用小括弧括起來。 Go 語言支援垃圾回收功能。 Go 語言的並列模型是以東尼 · 霍爾的交談循序程式( CSP )為基礎,採取類似模型的其他語言套件括 Occam 和 Limbo ,但它也具有 Pi 運算的特徵,比如通道傳輸。與 C++ 相比, Go 語言並不包括如例外處理、繼承、泛型、斷言、虛擬函式等功能,但增加了slice 型、並行、管道、垃圾回收、介面( interface )等特性的語言級支援。當然, Google 對於泛型的態度還是很開放的,但在該語言的常見問題列表中,對於斷言的存在,則持負面態度,同時也為自己不提供型別繼承來辯護。不同於 Java , Go 語言內嵌了關聯陣列(也稱為雜湊表( hashes )或字典( dictionaries )),就像字串類型一樣。

Golang 入門初體驗當前有兩個 Go 語言的編譯器的分支。官方編譯器 gc 和 gccgo 。官方編譯器在初期使用 C 寫成,後用 go 重寫從而實作自舉。 Gccgo 是一個使用標準 GCC 作為後端的 Go 編譯器。官方編譯器支援跨平台編譯(但不支援 CGO ),允許將源碼編譯為可在目標系統、架構上執行的二進位檔案。Go 在 2007 年 9 月開始為 google 內部員工的 20% 自由時間的實驗項目 , 目的為改善公司內部的開發速度 . 隔了一年受到 google 公司的重視和支持 , 2009 年 11 月對外發佈第一版本 . 過去 google 常用的語言有 c , java , python 來開發他們的服務 . 但是這些語言是在創造的時候的硬體環境 , 和現在有很大的不同 . 而 Golang 在設計的時候就考慮了這些架構 , 所以在開發上和過去的語言比較起來有很大的優勢 .

Go 的關鍵字不多 ( 目前約 25 個 c 37 個 ,c++ 84 個而且還在增加中 ), 學習上很容易上手 , 再加上有不少和 c 的關鍵字重複 , 所以如果你原本就是寫 c/c++ 的人 , 在轉換上會相對其他語言容易 .

平均下來效能是輸 c, 可是贏其他語言如 ruby(ror), php, java, python 等語言 . 主要原因的先天優勢是 , 他語言架構設計非常單純 , 並不像是如物件導向語言這麼的大 . 再來是他不是直譯語言 . 光是這兩點優勢 , 讓他有更好的體質去有較好的執行效率 . 已經有很多案例 , 在開發伺服器的軟體時 , 因為效能問題而改換 go 去寫 , 結果讓他們減少了伺服器的數量就可以達到原本的效能 , 增加了服務的效率也省下了硬體成本 .

Golang 入門初體驗goroutine這是 Go 的最大特色之一 , thread 的使用是非常消耗系統資源的 , 而且當你使用越來越多時管理上也越困難 . 而 goroutine 有輕巧低消耗資源的特性 , 而這優點在於系統資源的消耗也會比較少 .

可以使用 c library他可以使用 c library, 解決新語言在 library 上的不足 . 不過基本上 go 本身的 lib 已經很豐富了 . Unicode slice map 都有支援 .目前越來越多公司用 go 來寫他們網站後端的系統 , 過去 google 在寫他的服務大多使用 c + python, python 是個很簡潔的語言在建構服務是非常快的 , c 是用來解決效能上的問題 , 現在內部也都慢慢轉成 go 來寫 . 在 2014 年 TIOBE四月程式語言排名已經到第 30名 , 2013 基本上都是在 50名以外 , 而且我相信這名次會持續在進步 .

第一個 go 程式package mainimport "fmt“func main () { fmt.Println("Hello , World!")}

Golang 入門初體驗Go 安裝http://golang.org/dl/

Windows:安裝好 GO 後 , 還需要 GCC 編譯環境 , 因為 Mac 的 Xcode 本身就有 GCC, windows 沒有 , 所以選擇了 mingw 這個輕量型的 GCChttp://blog.jex.tw/blog/2013/12/17/windows-install-gcc-compiler-mingw/

設定 GOPATH, 這是放一些你自己或 go get from github 存放的地方1. 在 c:\Go 下建立 mygo 資料夾 , 並且在 mygo 下建立三個資料夾 src, pkg, bin2. 設定環境變數 GOROOT : C:\Go GOPATH : C:\Go\mygo PATH : 在最後面加上 ;%GOROOT%\bin;%GOPATH%\bin;C:\Program Files (x86)\Git\bin除了加上 GOROOT\bin 、 GOPATH\bin, 也可視需求加上 git bin 的路徑,重新啟動 cmd 使變數生效

Golang 入門初體驗Go 執行https://golang.org/cmd/go/

go run

要撰寫第一個 Hello, World 程式,你可以建立一個 main.go ,在當中撰寫以下的內容:package main

import "fmt"

func main() { fmt.Println("Hello, World") fmt.Println("哈囉!世界! ")}

Golang 入門初體驗Go 執行每個 .go 原始碼,都必須從 package 定義開始,而對於包括程式進入點 main 函式的 .go 原始碼,必須是在 package main 之中,為了要能輸出訊息,這邊使用了 fmt 套件( package )之中的 Println 函式,開頭的大寫 P 表示這是個公開的函式,可以在套件之外進行呼叫。Go 的創建者之一也是 UTF-8 的創建者,因此, Go 可以直接處理多國語言,只要你確定編輯器編碼為 UTF-8 就可以了,如果你使用 vim ,可以在 vim 的命令模式下輸入 :set encoding=utf-8 ,或者是在 .vimrc 之中增加一行 set encoding=utf-8 。Go 可以用直譯的方式來執行程式,第一個 Hello, World 程式就是這麼做的,執行 go run 指定你的原始碼檔名就可以了:$ go run main.goHello, World哈囉!世界!package 與 GOPATH

Golang 入門初體驗Go 執行那麼,一開始的 package 是怎麼回事?試著先來建立一個 hello.go:package hello

import "fmt"

func HelloWorld() { fmt.Println("Hello, World")}

Golang 入門初體驗Go 執行記得, package 中定義的函式,名稱必須是以大寫開頭,其他套件外的程式,才能進行呼叫,若函式名稱是小寫,那麼會是套件中才可以使用的函式。接著,原本的 main.go 修改為:package main

import "hello"

func main() { hello.HelloWorld()}

Golang 入門初體驗Go 執行現在顯然地, main.go 中要用到方才建立的 hello 套件中的 HelloWorld 函式,這時 package 的設定就會發揮一下效用,你得將 hello.go 移到 src/hello 目錄之中,也就是目錄名稱必須符合 package 設定之名稱。而 src/hello 的位置,必須是在 GOROOT 底下,或者是 GOPATH 底下,當 Go 需要某個某個套件中的元素時,會分別到這兩個環境變數的目錄之中,查看是否有相應於套件的原始碼存在。為了方便,通常會設定 GOPATH ,例如,指向目前的工作目錄:export GOPATH=~\workspace\go雖然目前 GOPATH 中只一個目錄,不過 GOPATH 中可以設定數個目錄

Golang 入門初體驗Go 執行go build

這個命令主要用於測試編譯。在包的編譯過程中,在必要情況下,還可以同時編譯與之關聯的包。- 普通包:執行完 go build , 不會產生任何檔,如果需要在 $GOPATH/pkg 下生成相應檔,則要執行 go install.

-main 包:執行完 go build,會在目前的目錄下生成一個可執行檔,如果需要在 $GOPATH/bin 下生成對應檔,需要執行 go install 或使用 go build -o outputpath/

如果只想編譯某個檔,只需在在後面加上檔案名即可,例如 :go build hello.go

非main 包在預設情況下編譯輸出的是 package名, main 包是第一個原始檔案的檔包,也還可以指定編譯輸出的檔案名,例如, go build -o xialingsc.exe

Golang 入門初體驗Go 執行go build

go build 會忽略目錄下以 "_" 或 "." 開頭的 go文件如果原始程式碼針對不同的作業系統需要不同的處理,那麼可以根據不同的作業系統尾碼來命名檔。例如, readfile_linux.go,readfile_drawin.go,readfile_windows.go.go build 會選擇性編譯檔, Linux 系統只編譯 readfile_linux.go ,其他文件則被忽略。

Golang 入門初體驗Go 執行go build

如果想編譯原始碼為可執行檔,那麼可以使用 go build ,例如,直接 go build main.go ,就會在執行指令的目錄下,產生一個名稱為 main 的可執行檔,可執行檔的名稱是來自己指定的原始碼檔案主檔名,執行產生出來的可執行檔就會顯示 Hello, World 。你也可以建立一個 bin 目錄,然後執行 go build -o bin/main main.go ,這樣產生出來的可執行檔,就會被放在 bin 底下,如果想將原始碼全部放在 src 底下管理,那麼就將 main.go 放到 src/main 底下,然後執行 go build -o bin/main src/main/main.go 。go install

每次使用 go build ,都是從原始碼編譯為可執行檔,這比較沒有效率,如果想要編譯時更有效率一些,可以使用 go install ,例如,在目前既有的目錄與原始碼架構之下,執行 go install main

Golang 入門初體驗Go 執行go build

~go |~bin/ | |-main |~pkg/ | `~linux_arm/ | `-hello.a |~src/ | |~hello/ | | `-hello.go | |~main/ | | `-main.go

go install packageName 表示要安裝指定名稱的套件,如果是 main 套件,那麼會在 bin 中產生可執行檔,如果是公用套件,那麼會在 pkg 目錄的 $GOOS_$GOARCH 目錄中產生 .a 檔案

Golang 入門初體驗Go 執行go build

可以使用 go env 來查看 Go 使用到的環境變數,例如:$ go envGOARCH="arm"GOBIN=""GOEXE=""GOHOSTARCH="arm"GOHOSTOS="linux"GOOS="linux"GOPATH="/root/workspace/go"GORACE=""GOROOT="/opt/go"GOTOOLDIR="/opt/go/pkg/tool/linux_arm"GO15VENDOREXPERIMENT=""CC="gcc"GOGCCFLAGS="-fPIC -marm -pthread -fmessage-length=0"CXX="g++"CGO_ENABLED="1"

Golang 入門初體驗Go 執行go build

.a 檔案是編譯過後的套件,因此,你看到的 hello.a ,就是 hello.go 編譯之後的結果,如果編譯時需要某個套件,而對應的 .a 檔案存在,且原始碼自上次編譯後未曾經過修改,那麼就會直接使用 .a 檔案,而不是從原始碼開始編譯起。

go clean用來移除當前源碼包裡編譯生成的檔,這些檔包括, _obj(舊的 objects 目錄, Makefiles遺留 ) ,_test(舊的 test 目錄 ) , _testmain.go (舊的 gotest文件), test.out 、 build.out (舊的test記錄)*.[568ao] object文件 ,DIR ( .exe ) ( 由 go build產生 ) , DIR.test ( .ext)( 由 go test -c產生 ) ,MAINFILE(.exe) ( 由 go build MAINFILE.go產生 )該命令最大的作用,清除編譯文件後,上傳 git ,保持源碼清潔。

Golang 入門初體驗Go 執行os.Args

那麼,如果想在執行 Go 程式時使用命令列引數呢?可以使用 os 套件的 Args ,例如,寫一個 main.go:package main

import "os"import "fmt"

func main() { fmt.Printf("Command: %s\n", os.Args[0]) fmt.Printf("Hello, %s\n", os.Args[1])}

Golang 入門初體驗Go 執行os.Args 是個陣列,索引從 0 開始,索引 0 會是編譯後的可執行檔名稱,索引 1 開始會是你提供的引數,例如,在執行過 go build 或 go install 之後,如下直接執行編譯出來的執行檔,會產生的訊息是…$ ./bin/main JustinCommand: ./bin/mainHello, Justingo doc

Golang 入門初體驗Go 執行fmt 的 Printf ,就像是 C 的 printf ,可用的格式控制字元可參考 Package fmt 的說明。實際上, Go 本身附帶了說明文件,可以執行 go doc <pkg> <sym>[.<method>] 來查詢說明。例如:$ go doc fmt.Printffunc Printf(format string, a ...interface{}) (n int, err error)

Printf formats according to a format specifier and writes to standard output. It returns the number of bytes written and any write error encountered.

Golang 入門初體驗Go 執行go fmt幫助格式化寫好的代碼檔,讓寫代碼時不關心格式,寫完後,輕鬆執行 go fmt 檔案名 .go 就好提高效率。更多的時候可以採用 gofmt, 同時增加 -w 的參數,否則格式化結果不會寫入檔,例如:gofmt -w src 來格式整個項目。

go get動態獲取遠端代碼。這個代碼內部分為兩部分,一是下載源碼包,另一步是執行 go install為了讓 go get 正常使用,需保證安裝了合適的源碼管理工具,並將這些命令加入到 PATH 中。可通過 go help remote 瞭解更多。

go test執行這個命令會自動讀取源碼目錄下名為 *_test.go 檔,生成並運行測試用的可執行檔。預設情況下不需要任何參數,也可帶上參數,具體可參見 go help testflag

Golang 入門初體驗Go 執行go doc如何查看相應的 package文檔呢?如果是 builtin 包,可以執行 go doc builtin; 如果是 http 包,執行 go doc net/http;查看某個包裡面的函數,類似執行 godoc fmt Println, 還可以查看相應代碼godoc -src fmt Println很棒的一點是,可以在終端執行 godoc -http=:埠號,例如 godoc -http=:8080 , 就可以在流覽器中敲入 127.0.0.1:8080 進行文檔內容的查看。其他命令go fix 用來修復以前老版本的代碼到新版本go version 查看 go 當前的版本go env 查看當前 go 的環境變數go list 列出當前全部安裝的 packagego run 編譯並運行 go 語言程式

Golang 入門初體驗Go IDEs

IDEs 有以下幾種,使用文字編輯器亦可完成程式編輯,再至 Command Line 執行編譯Lite IDE

GoSublime

Visual Studio Code

Goclipse

VIM / VIM-go

Golang 入門初體驗Go 語法 (參考 https://polor10101.gitbooks.io/golang_note/content/about_golang.html)

variable 變數雖然號稱和 c 類似 , 不過 go 在變數的命名上有很大的不同 , 他是以 type 放後面為原則以下表示宣告一個變數 x 且他是整數型態 .var x int

如果要給他初始值 , 有以下幾種方式給予初始值var x int = 10var y = 20

var z int z = 30

k := 40

常數const PI = 3.1415const NAME = "Nelson"常數不需要給定 type, 只要直接給予值即可 .

而且不能用 ':=' 的方式給予初始值 .

你可以發現每行的結尾少了分號 ';', 是的 go 省略分號 , 只要斷行來分就好 .

Golang 入門初體驗Go 語法函式函式的宣告方式也和 c 不同func add( a int , b int){ c := a + b}如果要回傳東西func add( a int , b int) int{ c := a + b return c}但是還是維持 type 在後面的原則在此在大括號的位置也有規定 , 如例子所示

Golang 入門初體驗Go 語法函式你也可以func add( a int , b int) int{ }但是不可以採用對齊的方式func add( a int , b int) int{

}從以上例子來看 , 會發現 go 在程式的風格上有強制規定 , 不符合規定會有 error

Golang 入門初體驗Go 語法函式有幾點初學的時候有點不習慣每行 code 不需要分號 ;大括號的排版方式import 某個 lib 或是宣告某變數但是實際卻沒有使用宣告改成這個方式 , 官方是說可以增加閱讀性 . why? 這部份在其他章節會有描述另外可以用變數直接指向 function

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

Golang 入門初體驗Go 語法關於語法go 在語法中做了改變 , 官方的說法是 , 改這樣的原因是因為這樣比較好閱讀 . 接著來比較 c 和 go 的語法以宣告 variable 來看int add; //cvar add int //gogo 還多一個 var, 似乎也沒有比較方便以 function 來看int add(int a, intb); //cfunc add(a int, b int)int //go也還好 , 不過比較起來 go 比較容易分辨出是 variable 還是 function, 但是差距也沒有很大 , 而且 go 還需要多打字 .

Golang 入門初體驗Go 語法關於語法如果宣告一個 function pointer 然後輸入一個 function pointer 變數int (*fp)(int (*ff)(int x, int y), int b) //cf func(func(int,int) int, int) int //goc 已經有點讓人混亂了 , 且不好閱讀 .

go 這樣是不是有比較清楚?個人是覺得有 , 還很明顯 .

再加個回傳 function pointer 的宣告比較 .

int (*(*fp)(int (*)(int, int), int))(int, int) //c f func(func(int,int) int, int) func(int, int) int //go結論 : 真的有差 .f := addf(10,20)

Golang 入門初體驗Go 語法loop 迴圈以 c 來說迴圈有 for , while , do while 三個關鍵字 . 在 go 裡面只有一個 for 來達到全部以 for 迴圈來說 c 為int i = 0;int sum = 0;for( i = 0 ; i < 10 ; ++i ){ sum += i;}以下為 go 的 for 迴圈範例sum := 0for i := 0 ; i < 10 ; ++i { sum += i}從範例中可以看到 "( )" 省略了 , 再來兩個中夸號並不是對齊的

Golang 入門初體驗Go 語法loop 迴圈c 的 do while 迴圈int i = 0;

do{ i += 1}while( i < 10 );

go 中為i := 0for i < 100 { i += 1}

c 的無限 while 迴圈while(1){

}

go 的for {

}以上為 go 和 c 的迴圈使用比較 . 從中可以了解一些語法上的差別

Golang 入門初體驗Go 語法if switch和 c 不同的是 , if 不需要小括號 , 另外大括號必須要有 , 並且強制是範例的排版方式 , 否則會有 error

if a > 0 { fmt.Println(a)}個人是習慣對齊的方法 , 不過這樣的強制規定可以讓多人開發專案的程式碼有一致性 , 這也是個好處 .

switch 也是同樣的規定 , 並且不需要加上 'break'

如以下所示switch i {case 1 : fmt.Println(1)case 2 : fmt.Println(2)default : fmt.Println("default")}

Golang 入門初體驗Go 語法Array

array 的宣告 var a [5]int // 宣告一個大小 5 的 int array var b []int // 宣告空的 int array var c = []int{1,2,3,4,5}// 宣告一個大小 5 的 int array, 並給初始值如果要列印 array 裡面的值 , 你可以使用 range 知道 array 目前的 index

import "fmt"

func main() { var a = [4]int {1,2,3,4}

for i := range a { fmt.Println(a[i]) }}

Golang 入門初體驗Go 語法Array

初始話也可以改成var a = [4]int { 1, 2, 3, 4,}

其中 4 的後面記得要有逗號 ','

Golang 入門初體驗Go 語法Slice

相對於 array, slice 提供了彈性 , 不需要指定大小是由三個元件組成ptrlencap其中 prt 是指向一個 array 的指標 , 如圖所示

Golang 入門初體驗Go 語法Slice

len 為實際資料長度 , cap 表示容量宣告的方式為 a := []int此時的 len , cap 都為 0

你可以用 make 來宣告變數並指定 len 和 cap 的值a := make([]int,5,10)其中 5 為 len, 10 為 cap

Golang 入門初體驗Go 語法Slice

也可以不指定 cap 寫成a := make([]int,5)這時 len 為 5 , cap 為 5

另外給定初始值的宣告var a = []int {1,2,3,4,5}如果要一個變數指向 a

b := a[2:4]c := a[:]

Golang 入門初體驗Go 語法Slice

其中b 為 3 , 4.

a[2:4] 的 2 表示指向 a index = 2 的位置 , 4 表示為結尾在 index = 4( 不包含 ), 所以則 length 為 2, 另外由於 array 是連續的 , 所以 cap = 3

c 為 1,2,3,4,5[:] 表示 [0:len(a)], 第一個值如果沒寫則為 0, 第二個沒寫的為 len(a) = 5

Golang 入門初體驗Go 語法Slice

所以可以得知a[:3] 和 a[1:] 個別為1,2,3 //a[:3]2,3,4,5 //a[1:]另外要特別注意的是 ,

b := a[1:3]並不是 a copy 值給 b, 而是將 b 的指標指向 a ( index 2 ) 的位置所以這表示如果你改了 b 的值 , a 的也會一起改變 , 因為他們共用同一個 array.如果你要兩者是獨立的 , 可以使用 copy 的方法copy(b,c)

Golang 入門初體驗Go 語法Slice

這是把 c 的前兩個的值 , copy 到 b 中 . 為什麼只 copy c 的前兩個?因為 b 的大小只有二 , 以小的為主copy(c,b)則表示將 b 的值 , copy 覆蓋 c 的前兩個值 .另外可以用 append 來增加他的大小如下所示import "fmt"

func main(){ var a[]int a = append(a,1) a = append(a,2)

for i := range a { fmt.Println(a[i]) }}

Golang 入門初體驗Go 語法Map

建立一個 map 可以用以下兩種方式m := make(map[string]int)m := map[string]int{}定義 age = 16

m["age"] = 16回傳值 , 和 map 是否存在value , ok := m["age"]如果是不存在的 key , value = 0 , ok = false

value , ok := m["height"]

Golang 入門初體驗Go 語法Map

利用 rage 取得 map 中所有的 key 和 value, 並列印for key, value := range m { fmt.Println("Key:", key, "Value:", value) }

如果要一次宣告多個值 , 可以用以下方式person := map[string]int{ "age" : 16, "height" : 180, "weight" : 6, }

https://play.golang.org/p/rPpLGhc9lE

Golang 入門初體驗Go 語法Goroutine

在講 goroutine 之前 , 必須先了解 concurrency 和 parllelism 的不同concurrency (併發 ) vs parallelism ( 並行 )

併發 concurrency 是將 process 切割成多個可執行單位 (coroutine), 如果有多個 process 時則在 coroutine 中交互執行 , 這樣不會因為某一個 process 執行太久造成其他 process 無法處理 , 會讓你有多工的感覺 . 但這並不會增加執行效率 . 總執行時間是一樣的 , 如圖一所示 . 另外和 thread 不同的是 , thread 是屬於 os 層面上的 , 且 thread 非常消耗系統資源 .

Golang 入門初體驗Go 語法Goroutine

Golang 入門初體驗Go 語法Goroutine

並行 parallelism 是多個 process 由多個 cpu 去執行 , 可以增加執行的效率 , 縮短總執行的時間 . 如圖二所示

Golang 入門初體驗Go 語法Goroutine

go 用 goroutine 實現了 concurrency. 使用者只要設定最大的 thread 數量 , 和系統是多少 CPU, run-time 就會幫你處理好剩下的 thread 管理 , 但為什麼 gorountine 可以提供較好的效能 ? ( 以下為個人心得可能有錯)

Golang 入門初體驗Go 語法Goroutine

第一 . 在創造 goroutine 的資源消耗是很小的 , 因為 goroutine 只是一個 function 的入口 . 只要很小的 stack 去記錄即可 .

第二 . thread 上的管理 , 當某個 goroutine 被 block 住 , 處理此 goroutine 的 thread 就耗在這上 , 此時 run-time 會將其他的 goroutine 轉給此 thread 去執行 , 這樣 thread 就不會因為執行某些被 block 的任務則消耗在那邊 . 有效的使用 thread.

第三 . thread 最多不會超過 $GOMAXPROCS( 使用者設定的 ), 可以確保 thread 不會失控的增加 .

綜合以上三點 , goroutine 其實就是避免 thread 的不當增加 , 有效的使用目前的 thread , 這樣系統資源的消耗就少了 . 不會因為系統的資源不夠造成整體效能的降低 .coroutine 資料一coroutine 資料二thread 資料

goroutine 資料一goroutine 資料二goroutine 資料三

Golang 入門初體驗Go 語法goroutine - channelsDon't communicate by sharing memory; share memory by communicating.不同於過去的 muliti thread 的程式開發 , 常使用共用變數去做資訊傳遞 (communicate by sharing memory).

goroutine 彼此的溝通方式是使用 channel (share memory by communicating). 這樣的方式會讓整個流程在表現上變得清楚 .

channelschannel 的通訊 , 需要一個 sender 和一個 receiver, 當 sender 和 receiver 都 ready 時候 , 訊息才會成功的傳遞 .

import "fmt"

func main(){ s := mack(chan string) // 宣告一個 channel 變數 s <- "hello" // 寫入 channel (sender) val <- s //讀取 channel (receiver) fmt.Println(val)}上例說明了如何宣告 channel 變數 , 寫入 channel, 讀取 channel.

Golang 入門初體驗Go 語法goroutine - channels但是 code 無法跑 , compiler 會顯示 dead lock.

因為執行到 s <- "hello" 這步的時候 , sender 就會進入 ready 狀態 .

然後就停住了 , 也就是他不會執行到 val := <- s .這時我們建立一個 goroutine 去跑 s <- "hello"

import "fmt"

func main(){ s := make(chan string) // 宣告一個 channel 變數 go func(){ s <- "hello" // 寫入 channel (sender) }()

val := <- s //讀取 channel (receiver) fmt.Println(val)}

Golang 入門初體驗Go 語法goroutine - channels執行順序就是1. 建立一個 goroutine //此時 s <- "hello" 還沒執行2. 執行 val := <- s //s 空的 , receiver ready (停住 )3. 執行 s <- "hello" //sender 將訊息寫入 s, sender ready4. val := <- s // 成功讀取 s, (因為 receiver, sender 都 ready)5. fmt.Println(val)goroutine 不見得會比較慢執行 . 不過重點不在這 , 就不多描述了 .

所以要看執行的順序就要注意這原則 " 當 sender 和 receiver 都 ready 時候 , 訊息才會成功的傳遞 . "

再舉一個例子 , 請問順序式如何?

Golang 入門初體驗Go 語法goroutine – channels

import "fmt"

func main(){ s := make(chan string) go func() { for i := 0; i < 3; i++ { fmt.Println("sender hello",i) s <- fmt.Sprintf("receiver hello %d", i) } }()

for i := 0; i < 3; i++ { val := <-s fmt.Println(val) }}

Golang 入門初體驗Go 語法goroutine – channels

結果sender hello 0sender hello 1receiver hello 0receiver hello 1sender hello 2receiver hello 2

有對嗎?程式碼順序是這樣1. 建立一個 goroutine 2. 執行 val := <- s //s 空的 , receiver ready (停住 )[sender hello 0]3. 執行 s <- fmt.Sprintf.. //sender 將訊息寫入 s, 傳訊成功[sender hello 1]4. 執行 s <- fmt.Sprintf.. //s 有值 , sender ready(停住 )5. val := <-s //讀到 s 的值[receiver hello 0]6. val := <-s //s 空的 , receiver ready, 傳訊成功[receiver hello 1]7. val := <-s //s 空的 , receiver ready (停住 )[sender hello 2]8. 執行 s <- fmt.Sprintf.. //sender 將訊息寫入 s, 傳訊成功9. val := <-s //讀到 s 的值[sender hello 2]

http://guzalexander.com/2013/12/06/golang-channels-tutorial.html

Golang 入門初體驗Go 語法goroutine – channels

另外我們可以修改 channel 的大小為 2

import "fmt"func main(){ s := make(chan string,2) go func() { for i := 0; i < 3; i++ { fmt.Println("sender hello",i) s <- fmt.Sprintf("receiver hello %d", i) } }()

for i := 0; i < 3; i++ { val := <-s fmt.Println(val) }}

結果sender hello 0sender hello 1sender hello 2receiver hello 0receiver hello 1receiver hello 2

http://guzalexander.com/2013/12/06/golang-channels-tutorial.html

Golang 入門初體驗Go 語法defer panic recover

defer

先看以下範例func f(){ for i := 0 ; i < 5 ; i++{ fmt.Println(i) } fmt.Println("f finish")}

此時的結果是01234f finish

Golang 入門初體驗Go 語法defer panic recover

如果再 fmt.Println 前面加上 defer

func f(){ for i := 0 ; i < 5 ; i++{ defer fmt.Println(i) } fmt.Println("f finish")}結果就變成f finish43210

Golang 入門初體驗Go 語法defer panic recover

defer 是讓 fmt.Println(0) , fmt.Println(1) , fmt.Println(2) , fmt.Println(3) , fmt.Println(4) 依序放到清單中 , 等到 func f 結束前 , 再依據 Last-In-First-Out (LIFO) 的順序 call 清單中的 function.

這樣功能提供了什麼的好處?我們再看以下的例子

Golang 入門初體驗Go 語法defer panic recover

func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return }

dst, err := os.Create(dstName) if err != nil { return }

written, err = io.Copy(dst, src) dst.Close() src.Close() return}

Golang 入門初體驗Go 語法defer panic recover

這個例子有個 bug, 就是當 os.Create(dstName) 失敗的時候 function 就會 return, 而 src.Close() 就沒有執行到了 .

所以可以改成這樣

Golang 入門初體驗Go 語法defer panic recover

func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return }

dst, err := os.Create(dstName) if err != nil { src.Close() return }

written, err = io.Copy(dst, src) dst.Close() src.Close() return}

Golang 入門初體驗Go 語法defer panic recover

但是如果開多個檔案 ? 那會很麻煩所以我們利用 defer 來修改func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } defer src.Close()

dst, err := os.Create(dstName) if err != nil { return } defer dst.Close()

return io.Copy(dst, src)}

這時不管如何 , 都會執行 close function.

Golang 入門初體驗Go 語法defer panic recover

但是如果開多個檔案 ? 那會很麻煩所以我們利用 defer 來修改func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } defer src.Close()

dst, err := os.Create(dstName) if err != nil { return } defer dst.Close()

return io.Copy(dst, src)}

這時不管如何 , 都會執行 close function.

參考網站https://blog.golang.org/defer-panic-and-recover

panic recovergo 沒有 try catch , 原因是 go 的開發者認為 , 當過多 try catch 時會造成程式碼很難閱讀 . 所以提供了 panic recover 優雅的解決這個問題 .

Golang 入門初體驗Go 後續

GoWeb

Go Bot (Line 、 Facebook Message 、 Slack 、 Telegram 、 …… ..)

Go 機器學習Go 物聯網

Golang 入門初體驗

top related