package main
import (
"fmt"
"runtime"
)
func main() {
var i byte
go func() {
for i = 0; i <= 255; i++ {
}
}()
fmt.Println("Dropping mic")
// Yield execution to force executing other goroutines
runtime.Gosched()
runtime.GC()
fmt.Println("Done")
}
涉及以下三個概念
func TestArgs(frist int, arg ...interface{}) {
//...Do something
}
意義明確,美觀
range 左邊的循環變數可以用以下方式來賦值
表達式左邊必須是可以尋址的或者是 map 索引表達式,如果表達式是 channel 只允許一個變數
range 右邊表達式的結果,可以是以下資料型別
package main
import "fmt"
func main() {
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
for k := range kvs {
fmt.Println("key:", k)
}
for i, c := range "go" {
fmt.Println(i, c)
}
}
輸出:
$ go run range.go
sum: 9
index: 1
a -> apple
b -> banana
key: a
key: b
0 103
1 111
type Foo struct {
bar string
}
func main() {
list := []Foo{
{"A"},
{"B"},
{"C"},
}
list2 := make([]*Foo, len(list))
for i, value := range list {
list2[i] = &value
}
fmt.Println(list[0], list[1], list[2])
fmt.Println(list2[0], list2[1], list2[2])
}
輸出:
// 實際輸出
{A} {B} {C}
&{C} &{C} &{C}
// 預期輸出
{A} {B} {C}
&{A} &{B} &{C}
根據以上的範例可以得知 Go 的 for…range 始終是值複製方式代替被遍歷的元素本身,簡而言之就是 range 中那個 value 是一個值拷貝,而不是元素本身當我們期望用 &value 獲得元素地址時,實際上只是取到了 value 這個臨時變數的地址,而非 List 中真正被遍歷到的某個元素的地址所以,在上面例子中 list2 被填充了三個相同的地址其實都是 value 的地址,而在最後一次循環中 value 被賦值為=={c}==,因此 list2 輸出時的時候顯示為 &{c}
正確的寫法如下:
for i, _ := range list {
list2[i] = &list[i]
}
這樣輸出list2中的元素,就能得到想到的結果
//example 1
func printArray(arr []int) {
}
//example 2
func printArray(arr [10]int) {
}
一般 Golang 最常使用 Slice
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s := arr[2:6]
fmt.Println("arr[2:6] = ", arr[2:6])
fmt.Println("arr[:6] = ", arr[:6])
fmt.Println("arr[2:] = ", arr[2:])
fmt.Println("arr[:] = ", arr[:])
}
//arr[2:6] = [2 3 4 5]
//arr[:6] = [0 1 2 3 4 5]
//arr[2:] = [2 3 4 5 6 7]
//arr[:] = [0 1 2 3 4 5 6 7]
上述的 s 就是一個Slice(切片)
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s := arr[2:6]
s[0] = 10
//arr -> [0, 1, 10, 3, 4, 5, 6, 7]
slice 擴展
slice 操作
向 slice 添加元素
s3 := append(s2, 10)
s4 := append(s3, 11)
s5 := append(s4, 12)
規則:
字節 => 位元組 => Byte => 一個位元組代表八個位元
Struct 是由一组field组成,每個field包括了名字(可選)和類型
為結構定義方法
func (node TreeNode) print() {
fmt.Print(node.value)
}
func (node *TreeNode) setValue(value) {
node.value = value
}
值接收者 vs 指針接收者
main
導入其他的包,包會被順序導入
import iii "fmt"
// 此处省略一些代码...
iii.Println("hello")
import . "fmt"
// 此处省略一些代码...
Println("hello")
:::info
有一個最特别的情況,如果别名是_的話,表示只註冊該包(初始化全局常數和變數,且執行其init函数),並不會實際引入該包。
:::
介面(接口/interface)由使用者來定義
介面的實現是隱式讀
只要實現介面裡的方法
介面包括引用(T,type)&值(v,value) or
介面(接口)裡面有什麼
將func接收者或回傳者改為interface{}
就可接受任何類型的變數
強制轉型 head.(int)
拿出的值轉為 int
系統接口組合
func funcName(input1 type1, intput2 type2) (output1 type1, output2 type2) {
return value1, value2
}
函數是一等公民:參數,變數,返回值都可以是函數
高階函數
函數 -> 閉包
函數也可以實現接口
函數實現範例
Go 語言閉包的應用
函數也可以通過宣告方式作為一種type
type addNum func(int, int) int
正統的函數式
何時使用 Defer
Panic 特性
Recover 特性
將flag綁定到一個變數
import "flat"
var flagvar int
func init() {
flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
}
綁定自定義的類型
import "flag"
// 自定義類型需要實做 value 接口
flag.Var(&flagVal, "name", "help message for flagname")
flag 解析
// 解析函數將會在碰到第一個非flag命令行參數時停止
flag.Parse()
命令行參數的格式
-flag xxx (使用空格,一個 - 符號)
––flag xxx (使用空格,兩個 - 符號)
-flag=xxx (使用等號,一個 - 符號)
––flag=xxx (使用等號,兩個 - 符號)
flag 範例
package main
import (
"flag"
"fmt"
)
var vehicleName = flag.String("name", "porsche", "Input the vehicle name.")
var vehicleYear = flag.Int("year", 10, "Input the vehicle year")
// func Args() []string
// Args returns the non-flag coomand-line arguments.
// func NArg() int
// NArg is the number of arguments remaining after flags have been processed.
func main() {
flag.Parse()
fmt.Printf("args=%s, num=%d\n", flag.Args(), flag.NArg())
for i := 0; i != flag.NArg(); i++ {
fmt.Printf("arg[%d]=%s\n", i, flag.Arg(i))
}
fmt.Println("vehicle name=", *vehicleName)
fmt.Println("vehicle year=", *vehicleYear)
}
執行
$ go run main.go -h
Usage of /var/folders/xf/hcn0238145n2w4cp_vk9vtcw0000gn/T/go-build233989185/b001/exe/main:
-name string
Input the vehicle name. (default "porsche")
-year int
Input the vehicle year (default 10)
exit status 2
-----------------------------------------------------
$ go run main.go name=toyota year=2019
args=[name=toyota year=2019], num=2
arg[0]=name=toyota
arg[1]=year=2019
vehicle name= porsche
vehicle year= 10
基本上使用與flag基本相同
新增功能:
增加 shorthand 參數
// func IntP(name, shorthand string, value int, usage string) *int
// IntP is like Int, but accepts a shorthand letter that can be used after a single dash.
var ip= flag.IntP("flagname", "f", 1234, "help message")
設定非必要選項的預設值
var ip = flag.IntP("flagname", "f", 1234, "help message")
flag.Lookup("flagname").NoOptDefVal = "4321"
結果如下圖:
Parsed Arguments | Resulting Value |
---|---|
-flagname=1357 | ip=1357 |
-flagname | ip=4321 |
[nothing] | ip=1234 |
flag 客製化
例如希望使用-
,_
或者.
,像--my-flag == --my_flag == --my.flag
:
func wordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
from := []string{"-", "_"}
to := "."
for _, sep := range from {
name = strings.Replace(name, sep, to, -1)
}
return pflag.NormalizedName(name)
}
myFlagSet.SetNormalizeFunc(wordSepNormalizeFunc)
例如希望聯合兩個參數,像--old-flag-name == --new-flag-name
:
func aliasNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
switch name {
case "old-flag-name":
name = "new-flag-name"
break
}
return pflag.NormalizedName(name)
}
myFlagSet.SetNormalizeFunc(aliasNormalizeFunc)
例如希望棄用名叫badflag
參數,並告知開發者使用代替參數:
// deprecate a flag by specifying its name and a usage message
flags.MarkDeprecated("badflag", "please use --good-flag instead")
例如希望保持使用noshorthandflag
,但想棄用簡稱n
:
// deprecate a flag shorthand by specifying its flag name and a usage message
flags.MarkShorthandDeprecated("noshorthandflag", "please use --noshorthandflag only")
隱藏flag
// hide a flag by specifying its name
flags.MarkHidden("secretFlag")
例如希望關閉對help文件或使用說明的flag排序:
flags.BoolP("verbose", "v", false, "verbose output")
flags.String("coolflag", "yeaah", "it's really cool flag")
flags.Int("usefulflag", 777, "sometimes it's very useful")
flags.SortFlags = false
flags.PrintDefaults()
輸出:
-v, --verbose verbose output
--coolflag string it's really cool flag (default "yeaah")
--usefulflag int sometimes it's very useful (default 777)
當處理JSON格式的時候,經常會看到宣告 Struct 結構如下:
type User struct {
UserId int `json:"user_id" bson:"user_id"`
UserName string `json:"user_name" bson:"user_name"`
}
理解此設計首先要先理解Golang命名採用駝峰(TestOne)方式
將上面定義的User轉成JSON格式:
u := &User{UserId: 1, UserName: "tony"}
j, _ := json.Marshal(u)
fmt.Println(string(j))
// 輸出内容:{"user_id":1,"user_name":"tony"}
如果未在屬性中增加標籤說明,則輸出:
{"UserId":1,"UserName":"tony"}
type T struct {
f1 string "f one"
f2 string
f3 string `f three`
f4, f5 int64 `f four and five`
}
不管是raw string還是interpreted string都可以用來當tag。 如果field定義時候兩個名字共用一個屬性,那麼這個tag會被附在兩個名字上,像f4,f5一樣。
package main
import (
"fmt"
"reflect"
)
type T struct {
f1 string "f one"
f2 string
f3 string `f three`
f4, f5 int64 `f four and five`
}
func main() {
t := reflect.TypeOf(T{})
f1, _ := t.FieldByName("f1")
fmt.Println(f1.Tag) // f one
f4, _ := t.FieldByName("f4")
fmt.Println(f4.Tag) // f four and five
f5, _ := t.FieldByName("f5")
fmt.Println(f5.Tag) // f four and five
}
設定一个空tag和不設定tag的效果一致
type T struct {
f1 string ``
f2 string
}
func main() {
t := reflect.TypeOf(T{})
f1, _ := t.FieldByName("f1")
fmt.Printf("%q\n", f1.Tag) // ""
f2, _ := t.FieldByName("f2")
fmt.Printf("%q\n", f2.Tag) // ""
}
Tags可以由鍵值對來组成,通過空格符號来分割鍵值 —key1:"value1" key2:"value2" key3:"value3"。如果Tags格式沒問題的化,我们可以通過Lookup或者Get來獲取鍵值對的值。
Lookup回傳兩個值 —
type T struct {
f string `one:"1" two:"2"blank:""`
}
func main() {
t := reflect.TypeOf(T{})
f, _ := t.FieldByName("f")
fmt.Println(f.Tag) // one:"1" two:"2"blank:""
v, ok := f.Tag.Lookup("one")
fmt.Printf("%s, %t\n", v, ok) // 1, true
v, ok = f.Tag.Lookup("blank")
fmt.Printf("%s, %t\n", v, ok) // , true
v, ok = f.Tag.Lookup("five")
fmt.Printf("%s, %t\n", v, ok) // , false
}
Get方法只是簡單的包装了以下Lookup。但是丟棄了是否成功结果
func (tag StructTag) Get(key string) string {
v, _ := tag.Lookup(key)
return v
}
type T1 struct {
f int `json:"foo"`
}
type T2 struct {
f int `json:"bar"`
}
t1 := T1{10}
var t2 T2
t2 = T2(t1)
fmt.Println(t2) // {10}
package main
type T struct {
f string "one two three"
}
func main() {}
> go vet tags.go
tags.go:4: struct field tag `one two three` not compatible with reflect.StructTag.Get: bad syntax for struct tag pair
Init 函數的主要作用:
Init 函數的主要特性:
Golang 程式初始化
Golang 程式初始化優先於 main 函數執行,由 runtime 進行初始化,初始化順去如下:
範例1:
main.go
package main import (
"fmt"
)
var T int64 = a()
func init() {
fmt.Println("init in main.go ")
}
func a() int64 {
fmt.Println("calling a()")
return 2
}
func main() {
fmt.Println("calling main")
}
輸出:
calling a()
init in main.go
calling main
初始化順序: 變數初始化 -> init() -> main()
範例2:
pack.go
package pack
import (
"fmt"
"test_util"
)
var Pack int = 6
func init() {
a := test_util.Util
fmt.Println("init pack ", a)
}
test_util.go
package test_util
import "fmt"
var Util int = 5
func init() {
fmt.Println("init test_util")
}
main.go
package main
import (
"fmt"
"pack"
"test_util"
)
func main() {
fmt.Println(pack.Pack)
fmt.Println(test_util.Util)
}
輸出:
init test_util
init pack 5
6
5
初始化順序:test_util -> pack
由於 pack package 的初始化依賴 test_util,因此運行時先初始化 test_util 在初始化 pack package
範例3:
sandbox.go
package main
import "fmt"
var _ int64 = s()
func init() {
fmt.Println("init in sandbox.go")
}
func s() int64 {
fmt.Println("calling s() in sandbox.go")
return 1
}
func main() {
fmt.Println("main")
}
a.go
package main
import "fmt"
var _ int64 = a()
func init() {
fmt.Println("init in a.go")
}
func a() int64 {
fmt.Println("calling a() in a.go")
return 2
}
z.go
package main
import "fmt"
var _ int64 = z()
func init() {
fmt.Println("init in z.go")
}
func z() int64 {
fmt.Println("calling z() in z.go")
return 3
}
輸出:
calling a() in a.go
calling s() in sandbox.go
calling z() in z.go
init in a.go
init in sandbox.go
init in z.go
main
同一個 package 不同 source file文件的 init 函數執行順序,golang spec 沒說明,以上程式輸出來看,執行順序是照source file名稱的字典排序(a-z)
範例4:
main.go
package main
import "fmt"
func init() {
fmt.Println("init")
}
func main() {
init()
}
init 函數不可以被調用,上面範例會提示:undefined: init
範例5:
main.go
package main
import "fmt"
func init() {
fmt.Println("init 1")
}
func init() {
fmt.Println("init 2")
}
func main() {
fmt.Println("main")
}
輸出:
init 1
init 2
main
init 函數比較特殊,可以在同一個 package 裡面被多次定義
範例6:
main.go
var initArg [20]int
func init() {
initArg[0] = 10
for i := 1; i < len(initArg); i++ {
initArg[i] = initArg[i-1] * 2
}
}
init 函數主要的用途:初始化不能使用初始化表達式初始化的變數
範例7:
import _ "net/http/pprof"
golang 對沒有使用的 pakcage 會編譯報錯,但有時候只想調用該 package 的 init函數,不使用pakcage導出的變數或是方法,因此就採用上面的導入方案
範例1:
package main
import "fmt"
var (
a int = b + 1
b int = 1
)
func main() {
fmt.Println(a)
fmt.Println(b)
}
package main
import "fmt"
func main() {
var (
a int = b + 1
b int = 1
)
fmt.Println(a)
fmt.Println(b)
}
輸出:
上述1
1
2
上述2
輸出不同的原因:不同作用域類型的變數初始化順序不同
上述2中的變數a,b是函數作用域內的局部變數,初始化順序為:從左到右、從上到下
範例2:
func f() int {
fmt.Println("f")
return 1
}
func g() int {
fmt.Println("g")
return 2
}
func h() int {
fmt.Println("h")
return 3
}
func main() {
var (
a int = f()
b int = g()
c int = h()
)
fmt.Println(a, b, c)
}
輸出:
f
g
h
1 2 3
但是對於範例1中package級別的變數,初始化順序與初始化依賴關係有關
範例3:
package main
import "fmt"
var (
a = c — 2
b = 2
c = f()
)
func f() int {
fmt.Printf("inside f and b = %d\n", b)
return b + 1
}
func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}
上述初始化的順序:
輸出:
inside f and b = 2
1
2
3
在每一個初始化週期,執行時會挑選一個沒有任何依賴的變數初始化,該過程一直持續到所有的變數均被初始化或者出現依賴嵌套的情況:
package main
import "fmt"
var (
a = b
b = c
c = f()
)
func f() int {
return a
}
func main() {
fmt.Println(a, b, c)
}
上述範例編譯器會提示錯誤:initialization loop
同一個package下多個文件的變數初始化依賴也遵循相同的規則:
tools.go
package main
import "fmt"
var (
a = c — 2
b = 2
)
func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}
utils.go
package main
var c = f()
func f() int {
return b + 1
}
輸出:
1
2
3
(fn_name)_test.go
func Test[fn_name](t *testing.T)
select
package main
import (
"fmt"
"time"
)
func main() {
go forever()
select {} // block forever
}
func forever() {
for {
fmt.Printf("%v+\n", time.Now())
time.Sleep(time.Second)
}
}
Go's runtime package
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
go func() {
time.Sleep(time.Second)
fmt.Println("Go 1")
}()
go func() {
time.Sleep(time.Second * 2)
fmt.Println("Go 2")
}()
runtime.Goexit()
fmt.Println("Exit")
}
channel
//example 1
// Receives the change in the number of goroutines
var goroutineDelta = make(chan int)
func main() {
go forever()
numGoroutines := 0
for diff := range goroutineDelta {
numGoroutines += diff
if numGoroutines == 0 { os.Exit(0) }
}
}
// Conceptual code
func forever() {
for {
if needToCreateANewGoroutine {
// Make sure to do this before "go f()", not within f()
goroutineDelta <- +1
go f()
}
}
}
// example 2
package main
import (
"fmt"
"time"
)
func main() {
done := make(chan bool)
go forever()
<-done // Block forever
}
func forever() {
for {
fmt.Printf("%v+\n", time.Now())
time.Sleep(time.Second)
}
}
Go sync.WaitGroup
var wg sync.WaitGroup
func main() {
// Create at least 1 goroutine
wg.Add(1)
go f()
go forever()
wg.Wait()
}
// Conceptual code
func forever() {
for {
if needToCreateANewGoroutine {
wg.Add(1)
go f()
}
}
}
func f() {
// When the termination condition for this goroutine is detected, do:
wg.Done()
}
卡死問題
Golang forever run
Golang block forever
Golang format
(未讀)好文
(未讀)Golang source code分析
(未讀)分散式Broker架構
golang init
golang goroutine 如何實現
goroutine 與調度器
scalable go scheduler design
goroutine模式
關於range
package 注意