Go語言入門分享
簡介:Go語言出自Ken Thompson、Rob Pike和Robert Griesemer之手,起源於2007年,並在2009年正式對外發布。Go的主要目標是“兼具Python等動態語言的開發速度和C/C++等編譯型語言的效能與安全性”,旨在不損失應用程式效能的情況下降低程式碼的複雜性,具有“部署簡單、併發性好、語言設計良好、執行效能好”等優勢。
作者 | 賦行
來源 | 阿里技術公眾號
前言
曾經我是一名以Java語言為主的開發者,做過JavaWeb相關的開發,後來轉Android,還是離不開Java,直到轉去做大前端了,其實也就是一直在用JS寫業務。如今由於個人發展原因,來到阿里雲,由於專案需要就擼起了Go語言;多年程式設計經驗告訴我,語言只是工具罷了,重要的還是其思想與邏輯,所以只需學學語法就好了,於是我便三天入門Go,期間主要用Java和JS來類比,語法變化之大,差點讓我從入門到放棄了!其實,還真不是學習語法就好了呢,其中包含了很多Go的設計理念。正所謂好記性不如敲爛鍵盤,學過的東西,還是要沉澱沉澱,也可以分享出來一起探討,更有助於成長,於是我就簡單記錄了一下我的Go語言入門學習筆記。
一 簡介
Go語言出自Ken Thompson、Rob Pike和Robert Griesemer之手,起源於2007年,並在2009年正式對外發布,其實都是Google的,設計Go語言的初衷都是為了滿足Google的需求。Go的主要目標是“兼具Python等動態語言的開發速度和C/C++等編譯型語言的效能與安全性”,旨在不損失應用程式效能的情況下降低程式碼的複雜性,具有“部署簡單、併發性好、語言設計良好、執行效能好”等優勢。最主要還是為了併發而生,併發是基於goroutine的,goroutine類似於執行緒,但並非執行緒,可以將goroutine理解為一種虛擬執行緒。Go語言執行時會參與排程goroutine,並將goroutine合理地分配到每個CPU中,最大限度地使用CPU效能。
二 環境
我們玩Java的時候需要下載JDK,類似於此,用Go開發也需要下載Go,裡面提供各種develop-kit、library以及編譯器。在官網下載mac版本pkg後直接安裝,最後用 go version 命令驗證版本:
然後就是設定這兩個環境變數,mac系統是在 .bash_profile 檔案裡面:
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
- GOROOT:表示的是Go語言編譯、工具、標準庫等的安裝路徑,其實就相當於配置JAVA_HOME那樣。
- GOPATH:這個和Java有點不一樣,Java裡並不需要設定這個變數,這個表示Go的工作目錄,是全域性的,當執行Go命令的時候會依賴這個目錄,相當於一個全域性的workspace。一般還會把$GOPATH/bin設定到PATH目錄,這樣編譯過的程式碼就可以直接執行了。
1 純文字開發
編寫程式碼,可以儲存在任意地方,例如新建一個helloworld目錄,建立hello.go檔案:
package main
import "fmt"
func main() {
fmt.Println("hello, world")
}
然後執行 go build hello.go 就可以編譯出hello檔案,在./hello就可以執行了;或者直接 go run hello.go 合二為一去執行。執行這個命令並不需要設定環境變數就可以了。看起來和c差不多,但是和Java不一樣,執行的時候不需要虛擬機器。早期的GO工程也是使用Makefile來編譯,後來有了強大的命令 go build、go run,可以直接識別目錄還是檔案。
2 GoLand
自動import,超爽的體驗!不用按command + /了!
執行專案需要設定build config,和Android、Java的都差不多,例如建立一個hello-goland專案:
匯入go module專案的時候需要勾選這項,否則無法像maven/gradle那樣sync下載依賴:
3 VSCODE
直接搜尋Go外掛,第一個最多安裝量的就是了,我還沒用過所以不太清楚如何。
三 工程結構
在設定GOPATH環境變數的時候,這個目錄裡面又分了三個子目錄bin、pkg、src,分別用於存放可執行檔案、包檔案和原始碼檔案。當我們執行Go命令的時候,如果我們指定的不是當前目錄的檔案或者絕對路徑的目錄的話,就會去GOPATH目錄的去找。這樣在GOPATH目錄建立了xxx的目錄後,就可以在任意地方執行 go build xx 命令來構建或者運行了。
pkg目錄應該是在執行 go install 後生成的包檔案,包括.a這樣的檔案,相當於一個歸檔。
├── bin
│ ├── air
│ ├── govendor
│ ├── swag
│ └── wire
├── pkg
│ ├── darwin_amd64
│ ├── mod
│ └── sumdb
└── src
├── calc
├── gin-blog
├── github.com
├── golang.org
├── google.golang.org
├── gopkg.in
└── simplemath
這樣對於我們具體專案來說並不好,沒有Workspace的概念來隔離每個專案了,所以我覺得這個GOPATH目錄放的應該是公用的專案,例如開源依賴的。我們在開發過程中,也會下載很多的依賴,這些依賴都下載到這個目錄,和我們的專案檔案混在一起了。
另外,通過IDE可以設定project的GOPATH,相當於在執行的時候給GOPATH增加了一個目錄變數,也就是說,我們建立一個專案,然後裡面也有bin、src、pkg這三個目錄,和GOPATH一樣的,本質上,IDE在執行的時候其實就是設定了一下GOPATH:
GOPATH=/Users/fuxing/develop/testgo/calc-outside:/Users/fuxing/develop/go #gosetup
Go語言在尋找變數、函式、類屬性及方法的時候,會先檢視GOPATH這個系統環境變數,然後根據該變數配置的路徑列表依次去對應路徑下的src目錄下根據包名查詢對應的目錄,如果對應目錄存在,則再到該目錄下查詢對應的變數、函式、類屬性和方法。
其實官方提供了Go Modules的方法更好解決。
1 Go Modules
從Go 1.11版本開始,官方提供了Go Modules管理專案和依賴,從1.13版本開始,更是預設開啟了對Go Modules的支援,使用Go Modules的好處是顯而易見的 —— 不需要再依賴GOPATH,你可以在任何位置建立Go專案,並且在國內,可以通過 GOPROXY 配置映象源加速依賴包的下載。也就是說,建立一個專案就是一個mod,基本上目前Go開源專案都是這樣做的。其實就是類似於Maven和Gradle。
// 建立mod專案,也是可以用IDE來new一個mod專案的:
go mod init calc-mod
// 一般開源在github上面的專案名字是這樣的;和maven、gradle不一樣的是,開發完成根本不需要釋出到倉庫!只要提交程式碼後打tag就可以了
go mod init github.com/fuxing-repo/fuxing-module-name
// 建立一個模組:執行這個命令主要是多了一個go.mod檔案,裡面就一行內容:
module calc-mod
// import以後,執行下載依賴命令,不需要編輯go.mod檔案。依賴會下載到GOPATH/pkg/mod目錄
go list
用GoLand來開啟不同的專案,顯示依賴的外部庫是不一樣的,如果是用GOPATH建立的專案,需要用命令下載依賴包到GOPATH:
go get -u github.com/fuxing-repo/fuxing-module-name
四 語法
1 包:Package 和 Import
Java裡面的包名一般是很長的,和資料夾名稱對應,作用就是名稱空間,引入的時候需要寫長長的一串,也可以用萬用字元:
Go裡面一般的包名是當前的資料夾名稱,同一個專案裡面,可以存在同樣的包名,如果同時都需要引用同樣包名的時候,就可以用alias區分,類似於JS那樣。一般import的是一個包,不像Java那樣import具體的類。同一個包內,不同檔案,但是裡面的東西是可以使用的,不需要import。這有點類似於C的include吧。如果多行的話,用括號換行包起來。
Go語言中,無論是變數、函式還是類屬性及方法,它們的可見性都是與包相關聯的,而不是類似Java那樣,類屬性和方法的可見性封裝在對應的類中,然後通過 private、protected 和 public 這些關鍵字來描述其可見性,Go語言沒有這些關鍵字,和變數和函式一樣,對應Go語言的自定義類來說,屬性和方法的可見性根據其首字母大小寫來決定,如果屬性名或方法名首字母大寫,則可以在其他包中直接訪問這些屬性和方法,否則只能在包內訪問,所以Go語言中的可見性都是包一級的,而不是類一級的。
在Java裡面,只有靜態,或者物件就可以使用點運算子,而且是極其常用的操作,而在Go裡面,還可以用一個包名來點,這就是結合了import來使用,可以點出一個函式呼叫,也可以點出一個結構體,一個介面。另外區別於C,不管是指標地址,還是物件引用,都是用點運算子,不需要考慮用點還是箭頭了!
入口的package必須是main,否則可以編譯成功,但是跑不起來:
Compiled binary cannot be executed.
原因就是找不到入口函式,跟C和Java一樣吧,也需要main函式。
2 變數
- 用 var 關鍵字修飾(類似於JS),有多個變數的時候用括號 () 包起來,預設是有初始化值的,和Java一樣。
- 如果初始化的時候就賦值了那可以不需要 var 來修飾,和Java不同的是變數型別在變數後面而不是前面,不過需要 := 符號。
- 最大的變化就是型別在變數後面!
- 語句可以省略分號 ;
var v1 int = 10 // 方式一,常規的初始化操作
var v2 = 10 // 方式二,此時變數型別會被編譯器自動推匯出來
v3 := 10 // 方式三,可以省略 var,編譯器可以自動推匯出v3的型別
//java
private HashMap<String, UGCUserDetail> mBlockInfo;
多重賦值
i, j = j, i
可以實現變數交換,有點像JS的物件析構,但是其實不一樣。有了這個能力,函式是可以返回多個值了!
匿名變數
用 _ 來表示,作用就是可以避免建立定義一些無意義的變數,還有就是不會分配記憶體。
指標變數
和C語言一樣的,回想一下交換值的例子即可,到底傳值和傳址作為引數的區別是啥。
Go語言之所以引入指標型別,主要基於兩點考慮,一個是為程式設計師提供操作變數對應記憶體資料結構的能力;另一個是為了提高程式的效能(指標可以直接指向某個變數值的記憶體地址,可以極大節省記憶體空間,操作效率也更高),這在系統程式設計、作業系統或者網路應用中是不容忽視的因素。
指標在Go語言中有兩個使用場景:型別指標和陣列切片。
作為型別指標時,允許對這個指標型別的資料進行修改指向其它記憶體地址,傳遞資料時如果使用指標則無須拷貝資料從而節省記憶體空間,此外和C語言中的指標不同,Go語言中的型別指標不能進行偏移和運算,因此更為安全。
變數型別
Go語言內建對以下這些基本資料型別的支援:
- 布林型別:bool
- 整型:int8、byte、int16、int、uint、uintptr 等
- 浮點型別:float32、float64
- 複數型別:complex64、complex128
- 字串:string
- 字元型別:rune,本質上是uint32
- 錯誤型別:error
此外,Go語言也支援以下這些複合型別:
- 指標(pointer)
- 陣列(array)
- 切片(slice)
- 字典(map)
- 通道(chan)
- 結構體(struct)
- 介面(interface)
還有const常量,iota這個預定義常量用來定義列舉。可以被認為是一個可被編譯器修改的常量,在每一個const關鍵字出現時被重置為0,然後在下一個const出現之前,每出現一次iota,其所代表的數字會自動增1。
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
numberOfDays
)
型別強轉
v1 := 99.99
v2 := int(v1) // v2 = 99
v1 := []byte{'h', 'e', 'l', 'l', 'o'}
v2 := string(v1) // v2 = hello
//字元相關的轉化一般用strconv包
v1 := "100"
v2, err := strconv.Atoi(v1) // 將字串轉化為整型,v2 = 100
v3 := 100
v4 := strconv.Itoa(v3) // 將整型轉化為字串, v4 = "100"
//結構體型別轉換
//型別斷言
//x.(T) 其實就是判斷 T 是否實現了 x 介面,如果實現了,就把 x 介面型別具體化為 T 型別;
claims, ok := tokenClaims.Claims.(*jwt.StandardClaims)
陣列與切片
//定義陣列
var a [8]byte // 長度為8的陣列,每個元素為一個位元組
var b [3][3]int // 二維陣列(9宮格)
var c [3][3][3]float64 // 三維陣列(立體的9宮格)
var d = [3]int{1, 2, 3} // 宣告時初始化
var e = new([3]string) // 通過 new 初始化
var f = make([]string, 3) // 通過 make初始化
//初始化
a := [5]int{1,2,3,4,5}
b := [...]int{1, 2, 3}
//切片
b := []int{} //陣列切片slice就是一個可變長陣列
c := a[1:3] // 有點類似於subString,或者js.slice
d := make([]int, 5) //make相當於,new、alloc,用來分配記憶體
//陣列的長度
length := len(a)
//新增一個元素
b = append(b, 4)
字典
其實就是Java裡的map,使用上語法有很多不同。
var testMap map[string]int
testMap = map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
//還可以這樣初始化:
var testMap = make(map[string]int) //map[string]int{}
testMap["one"] = 1
testMap["two"] = 2
testMap["three"] = 3
make和new
// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
// Slice: The size specifies the length. The capacity of the slice is
// equal to its length. A second integer argument may be provided to
// specify a different capacity; it must be no smaller than the
// length. For example, make([]int, 0, 10) allocates an underlying array
// of size 10 and returns a slice of length 0 and capacity 10 that is
// backed by this underlying array.
// Map: An empty map is allocated with enough space to hold the
// specified number of elements. The size may be omitted, in which case
// a small starting size is allocated.
// Channel: The channel's buffer is initialized with the specified
// buffer capacity. If zero, or the size is omitted, the channel is
// unbuffered.
func make(t Type, size ...IntegerType) Type
// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type
區別就是返回值和引數不同,一個是值,一個是指標,slice、chan、map只能用make,本身就是指標。其他make、new都行。
神奇的nil
Java裡面用null比較舒服,直接就判空了,除了在string型別的時候,還要判斷字元為 "",但是Go裡面的string要判斷為空就簡單一點,不能判斷nil,只能判斷 ""。然而Go裡面的nil卻和null不一樣,其實是和JS裡面 ==、=== 很像。
nil也是有型別的。
func Foo() error {
var err *os.PathError = nil
// …
return err //實際返回的是[nil, *os.PathError]
//return nil //正確的方式是直接return nil 實際返回的是[nil, nil]
}
func main() {
err := Foo()
fmt.Println(err) // <nil>
fmt.Println(err == nil) // false
fmt.Println(err == (*os.PathError)(nil)) //true
}
根物件:Object
在Java裡面,如果不用多型,沒有介面,父類,超類的話,就用Object作為根物件,在Go裡面,如果函式引數不知道用什麼型別,通常會用 interface{},這是個空介面,表示任意型別,因為不是弱型別語言,沒有any型別,也不是強面嚮物件語言,沒有Object,所以就有這個空介面的出現。
3 語句
比較大的一個特點就是能不用括號的地方都不用了。
控制流程
if語句的判斷條件都沒有了括號包起來,還可以前置寫變數初始化語句,類似於for迴圈,左花括號 { 必須與 if 或者 else 處於同一行。
switch語句變得更強大了,有這些變化:
- switch關鍵字後面可以不跟變數,這樣case後面就必須跟條件表示式,其實本質上就是美化了if-else-if。
- 如果switch後面跟變數,case也變得強大了,可以出現多個結果選項,通過逗號分隔。
- swtich後面還可以跟一個函式。
- 不需要用break來明確退出一個case,如果要穿透執行一層,可以用 fallthrough 關鍵字。
score := 100
switch score {
case 90, 100:
fmt.Println("Grade: A")
case 80:
fmt.Println("Grade: B")
case 70:
fmt.Println("Grade: C")
case 60:
case 65:
fmt.Println("Grade: D")
default:
fmt.Println("Grade: F")
}
s := "hello"
switch {
case s == "hello":
fmt.Println("hello")
fallthrough
case s == "xxxx":
fmt.Println("xxxx")
case s != "world":
fmt.Println("world")
}
//output:hello xxxx
迴圈流程
去掉了 while、repeat 這些關鍵字了,只保留了 for 這個關鍵字,其實用起來差不多。break , continue 這些關鍵字還是有的。
//通用的用法
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
//類似於while的用法
a := 1
for a <= 5 {
fmt.Println(a)
a ++
}
//死迴圈
for {
// do something
}
for ;; {
// do something
}
//類似java for-each的用法
listArray := [...]string{"xiaobi", "xiaoda", "xiaoji"}
for index, item := range listArray {
fmt.Printf("hello, %d, %s\n", index, item)
}
//java
for (String item : someList) {
System.out.println(item);
}
跳轉流程
Go很神奇的保留了一直被放棄的goto語句,記得是Basic、Pascal那些語言才會有,不知道為啥。
i := 1
flag:
for i <= 10 {
if i%2 == 1 {
i++
goto flag
}
fmt.Println(i)
i++
}
defer流程有點像Java裡面的finally,保證了一定能執行,我感覺底層也是goto的實現吧。在後面跟一個函式的呼叫,就能實現將這個xxx函式的呼叫延遲到當前函式執行完後再執行。
這是壓棧的變數快照實現。
func printName(name string) {
fmt.Println(name)
}
func main() {
name := "go"
defer printName(name) // output: go
name = "python"
defer printName(name) // output: python
name = "java"
printName(name) // output: java
}
//output:
java
python
go
//defer後於return執行
var name string = "go"
func myfunc() string {
defer func() {
name = "python"
}()
fmt.Printf("myfunc 函式裡的name:%s\n", name)
return name
}
func main() {
myname := myfunc()
fmt.Printf("main 函式裡的name: %s\n", name)
fmt.Println("main 函式裡的myname: ", myname)
}
//output:
myfunc 函式裡的name:go
main 函式裡的name: python
main 函式裡的myname: go
4 函式
- 關鍵字是 func,Java則完全沒有 function 關鍵字,而是用 public、void 等等這樣的關鍵字,JS也可以用箭頭函式來去掉 function 關鍵字了。
- 函式的花括號強制要求在首行的末尾。
- 可以返回多個值!返回值的型別定義在引數後面了,而不是一開始定義函式就需要寫上,跟定義變數一樣,引數的型別定義也是一樣在後面的,如果相同則保留最右邊的型別,其他省略。
- 可以顯式聲明瞭返回值就可以了,必須每個返回值都顯式,就可以省略 return 變數。
//一個返回值
func GetEventHandleMsg(code int) string {
msg, ok := EventHandleMsgMaps[code]
if ok {
return msg
}
return ""
}
//多個返回值
func GetEventHandleMsg(code int) (string, error) {
msg, ok := EventHandleMsgMaps[code]
if ok {
return msg, nil
}
return "", nil
}
//不顯式return變數值
func GetEventHandleMsg(code int) (msg string, e error) {
var ok bool
msg, ok = EventHandleMsgMaps[code]
if ok {
//do something
return
}
return
}
匿名函式和閉包
在Java裡面的實現一般是內部類、匿名物件,不能通過方法傳遞函式作為引數,只能傳一個物件,實現介面。
Go則和JS一樣方便,可以傳遞函式,定義匿名函式。
//傳遞匿名函式
func main() {
i := 10
add := func (a, b int) {
fmt.Printf("Variable i from main func: %d\n", i)
fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}
callback(1, add);
}
func callback(x int, f func(int, int)) {
f(x, 2)
}
//return 匿名函式
func main() {
f := addfunc(1)
fmt.Println(f(2))
}
func addfunc(a int) func(b int) int {
return func(b int) int {
return a + b
}
}
不定引數
和Java類似,不同的是在呼叫是也需要用 ... 來標識。
//定義
func SkipHandler(c *gin.Context, skippers ...SkipperFunc) bool {
for _, skipper := range skippers {
if skipper(c) {
return true
}
}
return false
}
//呼叫
middlewares.SkipHandler(c, skippers...)
五 面向物件
在C語言裡面經常會有用到別名的用法,可以用 type 類起一個別名,很常用,特別是在看原始碼的時候經常出現:
type Integer int
1 類
沒有 class 的定義,Go裡面的類是用結構體來定義的。
type Student struct {
id uint
name string
male bool
score float64
}
//沒有建構函式,但是可以用函式來建立例項物件,並且可以指定欄位初始化,類似於Java裡面的靜態工廠方法
func NewStudent(id uint, name string, male bool, score float64) *Student {
return &Student{id, name, male, score}
}
func NewStudent2(id uint, name string, male bool, score float64) Student {
return Student{id, name, male, score}
}
2 成員方法
定義類的成員函式方法比較隱式,方向是反的,不是宣告這個類有哪些成員方法,而是宣告這個函式是屬於哪個類的。宣告語法就是在 func 關鍵字之後,函式名之前,注意不要把Java的返回值定義給混淆了!
//這種宣告方式和C++一樣的,這個就是不是普通函數了,而是成員函式。
//注意到的是,兩個方法一個宣告的是地址,一個宣告的是結構體,兩個都能直接通過點操作。
func (s Student) GetName() string {
return s.name
}
func (s *Student) SetName(name string) {
s.name = name
}
//使用
func main() {
//a是指標型別
a := NewStudent(1, "aa", false, 45)
a.SetName("aaa")
fmt.Printf("a name:%s\n", a.GetName())
b := NewStudent2(2, "bb", false, 55)
b.SetName("bbb")
fmt.Printf("b name:%s\n", b.GetName())
}
//如果SetName方法和GetName方法歸屬於Student,而不是*Student的話,那麼修改名字就會不成功
//本質上,宣告成員函式,就是在非函式引數的地方來傳遞物件、指標、或者說是引用,也就是變相傳遞this指標
//所以才會出現修改名字不成功的case
3 繼承
沒有 extend 關鍵字,也就沒有了繼承,只能通過組合的方式來實現。組合就解決了多繼承問題,而且多繼承的順序不同,記憶體結構也不同。
type Animal struct {
name string
}
func (a Animal) FavorFood() string {
return "FavorFood..."
}
func (a Animal) Call() string {
return "Voice..."
}
type Dog struct {
Animal
}
func (d Dog) Call() string {
return "汪汪汪"
}
//第二種方式,在初始化就需要指定地址,其他都沒變化
type Dog2 struct {
*Animal
}
func test() {
d1 := Dog{}
d1.name = "mydog"
d2 := Dog2{}
d2.name = "mydog2"
//結構體是值型別,如果傳入值變數的話,實際上傳入的是結構體值的副本,對記憶體耗費更大,
//所以傳入指標效能更好
a := Animal{"ddog"}
d3 := Dog{a}
d4 := Dog2{&a}
}
這種語法並不是像Java裡面的組合,使用成員變數,而是直接引用Animal並沒有定義變數名稱(當然也是可以的,不過沒必要了),然後就可以訪問Animal中的所有屬性和方法(如果兩個類不在同一個包中,只能訪問父類中首字母大寫的公共屬性和方法),還可以實現方法重寫。
4 介面
Java的介面是侵入式的,指的是實現類必須明確宣告自己實現了某個介面。帶來的問題就是,如果介面改了,實現類都必須改,所以以前總是會有一個抽象類在中間。
//定義介面:
type Phone interface {
call()
}
//實現介面:
type IPhone struct {
name string
}
func (phone IPhone) call() {
fmt.Println("Iphone calling.")
}
Go的介面是非侵入式的,因為類與介面的實現關係不是通過顯式宣告,而是系統根據兩者的方法集合進行判斷。一個類必須實現介面所有的方法才算是實現了這個介面。介面之間的繼承和類的繼承一樣,通過組合實現,多型的實現邏輯是一樣的,如果介面A的方法列表是介面B的方法列表的子集,那麼介面B可以賦值給介面A。
六 併發程式設計
目前併發程式設計方面還沒學習多少,就簡單從網上摘了這一個經典的生產者消費者模型例子來初步感受一下,後續深入學習過後再進行分享。
// 資料生產者
func producer(header string, channel chan<- string) {
// 無限迴圈, 不停地生產資料
for {
// 將隨機數和字串格式化為字串傳送給通道
channel <- fmt.Sprintf("%s: %v", header, rand.Int31())
// 等待1秒
time.Sleep(time.Second)
}
}
// 資料消費者
func customer(channel <-chan string) {
// 不停地獲取資料
for {
// 從通道中取出資料, 此處會阻塞直到通道中返回資料
message := <-channel
// 列印資料
fmt.Println(message)
}
}
func main() {
// 建立一個字串型別的通道
channel := make(chan string)
// 建立producer()函式的併發goroutine
go producer("cat", channel)
go producer("dog", channel)
// 資料消費函式
customer(channel)
}
//output:
dog: 1298498081
cat: 2019727887
cat: 1427131847
dog: 939984059
dog: 1474941318
cat: 911902081
cat: 140954425
dog: 336122540
七 總結
這只是一個簡單入門,其實Go還有很多很多東西我沒有去涉及的,例如context、try-catch、併發相關(如鎖等)、Web開發相關的、資料庫相關的。以此貼開始,後續繼續學習Go語言分享。
本文為阿里雲原創內容,未經允許不得轉載。