- 程序入口文件的包名必須是main,但主程序文件所在文件夾名稱不必須是
main
,即我們下圖hello_world.go
在main
中,所以感覺(jué)package main
寫(xiě)順理成章,但是如果我們把main
目錄名稱改成隨便的名字如filename
也是可以運(yùn)行的,所以迷思就在于寫(xiě)在文件開(kāi)頭的那個(gè)package main
和java
中不是一個(gè)概念。主程序中函數(shù)是固定的。運(yùn)行這個(gè)文件用go run hello_world.go
,使用go build hello_world.go
會(huì)在同目錄下生成一個(gè)執(zhí)行文件,在windows上就是一個(gè)同名的exe
文件,如hello_world.exe
,使用.\hello_world.exe
也可以執(zhí)行得到結(jié)果。 -
main
函數(shù)沒(méi)有參數(shù),但它可直接通過(guò)os.Args
獲取,os.Args
是個(gè)數(shù)組,如以下代碼:
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println(os.Args)
if len(os.Args) > 1 {
fmt.Println("Hello Go...", os.Args[1])
}
os.Exit(0)
}
- 快速測(cè)試的方法,單元測(cè)試。文件名用
_test.go
結(jié)尾,方法用TestXXX
開(kāi)頭。包名和其他不論。在VS Code
中左邊會(huì)有一個(gè)按鈕,點(diǎn)擊即可測(cè)試,在其他編輯器中,可能點(diǎn)擊保存就會(huì)運(yùn)行。 - 以下是變量使用方法。go支持變量類型推斷,即可以省略類型定義。
func TestFib(t *testing.T) {
var a int
a = 1
var b int = 1
// var (
// a int
// b int = 1
// )
t.Log(a)
for i := 1; i < 10; i++ {
t.Log(b)
tmp := a
a = b
b = tmp + a
}
}
以下這個(gè)一句語(yǔ)句對(duì)多個(gè)變量賦值也是常用交換功能了:
func TestSwap(t *testing.T) {
a := 1
b := 2
a, b = b, a
t.Log(a, b)
}
- 常量使用。注意
iota
是個(gè)語(yǔ)法糖,出現(xiàn)時(shí)會(huì)被設(shè)置位0,代碼運(yùn)行到下一行時(shí)iota
自動(dòng)加1,所以以下用到它的代碼定義的常量是左移操作,每次乘以2。
const (
Monday = 1
Tuesday = 2
)
const (
Friday = 1 << iota
Saturday
Sunday
)
const ttt int = 1
func TestWeekDay(t *testing.T) {
t.Log(ttt)
t.Log(Monday, Tuesday)
t.Log(Friday, Saturday, Sunday)
a := 7 // 0111
t.Log(a&Friday == Friday, a&Saturday == Saturday, a&Sunday == Sunday)
}
- 基本數(shù)據(jù)類型后面會(huì)添加位數(shù),C#和其他語(yǔ)言中也會(huì)有部分影子。注意的是go不支持?jǐn)?shù)據(jù)類型
隱式
轉(zhuǎn)換。
bool
string
uint8 uint16 uint32 uint64 int8 int16 int32 int64
float32、float64
complex64 complex128
byte 類似 uint8
rune 類似 int32
uint:32 或 64 位
uintptr:無(wú)符號(hào)整型,用于存放一個(gè)指針
// 非要轉(zhuǎn)換的話,就顯示轉(zhuǎn)換
b = int64(a)
go語(yǔ)言的指針不支持運(yùn)算
func TestPointer(t *testing.T) {
a := 1
aPtr := &a
// aPtr = aPtr + 1 // 不支持指針運(yùn)算
t.Log(a, aPtr) //1 0xc000018320
t.Logf("%T %T", a, aPtr) //int *int
}
string
默認(rèn)是空字符串,而不是null
或nil
。
func TestString(t *testing.T) {
var s string
t.Log("*" + s + "*") //**
t.Log(s == "") //true
}
- go中沒(méi)有前置的自增和自減,之后后置的
a++
和a--
。go中數(shù)組的比較不是比較引用地址,而是直接比較數(shù)組長(zhǎng)度和值,只有數(shù)組長(zhǎng)度相同才能比較,不然直接編譯報(bào)錯(cuò),其次每個(gè)元素都相等,數(shù)組才相等。
func TestArray(t *testing.T) {
a := [...]int{1, 2, 3, 4}
//b := [...]int{1, 2, 3, 4, 5}
c := [...]int{1, 2, 3, 4}
d := [...]int{1, 2, 3, 5}
//t.Log(a == b) // 直接編譯報(bào)錯(cuò)
t.Log(a == c) //true
t.Log(a == d) //false
}
有個(gè)按位清零的運(yùn)算符&^
,即當(dāng)右邊運(yùn)算數(shù)的位為1時(shí)結(jié)果直接清零,如果是0則左邊運(yùn)算數(shù)的位是什么結(jié)果就是什么。相當(dāng)于右邊操作數(shù)對(duì)左邊操作數(shù)定向位清零。
a := 7 // 0111
Readable := 1 << 0 // 00000001
a = a &^ Readable // 結(jié)果是0110賦值給a
- 控制語(yǔ)句。go語(yǔ)言關(guān)鍵字很少大概20多個(gè),這體現(xiàn)在它的循環(huán)只有for循環(huán)一種。
n := 0
for n < 5 {
n++
...
}
// 無(wú)限循環(huán)
for {
...
}
// 條件語(yǔ)句
if a == b {
} else if c == d {
} else {
}
// 比較特殊的是if后面可以有賦值和條件,如前面定義個(gè)a后面條件就用這個(gè)a
if res,err := someFunc(), err == nil {
// 如果錯(cuò)誤為空,即返回正確則...
} else {
}
// switch不局限于常量或整數(shù),還可以充當(dāng)if的角色,case后面可以寫(xiě)多個(gè)匹配項(xiàng),以省略break。
switch i {
case 0,2: // 可以多個(gè)項(xiàng)
... // 可以省略break
case 1:
...
default:
...
}
switch {
case i > 1 && i < 3: // 類似if判斷語(yǔ)句
...
default:
....
}
- 數(shù)組相關(guān)。
func TestArrayLearning(t *testing.T) {
var a [3]int
a[0] = 1
t.Log(a) // [1 0 0]
arr1 := [4]int{1, 2, 3}
t.Log(arr1) // [1 2 3 0] 沒(méi)賦值的初始化為0
arr2 := [...]int{1, 3, 5, 7}
t.Log(arr2) //[...]表示數(shù)組長(zhǎng)度會(huì)按照后面的值得數(shù)量初始化
for i := 0; i < len(arr2); i++ {
t.Log(arr2[i])
}
// 快捷方法
for idx, v := range arr2 {
t.Log(idx, v)
}
// 如果不要索引,則用下劃線占位,不能直接去掉,不然報(bào)錯(cuò)
for _, v := range arr2 {
t.Log(v)
}
// 包含開(kāi)始,不包含結(jié)束
t.Log(arr2[1:3]) //[3 5]
t.Log(arr2[1:]) //[3 5 7]
t.Log(arr2[:3]) //[1 3 5]
}
- 切片相關(guān)。
func TestSlice(t *testing.T) {
var s0 []int
t.Log(len(s0), cap(s0)) //0
s0 = append(s0, 1)
t.Log(len(s0), cap(s0)) //1 1
s1 := []int{1, 2, 3, 4}
t.Log(len(s1), cap(s1)) //4 4
s2 := make([]int, 3, 5)
//t.Log(s2[0], s2[1], s2[2], s2[3])// 會(huì)報(bào)錯(cuò),因?yàn)殡m然初始化容量是5,但是只有3個(gè)元素可訪問(wèn)
s2 = append(s2, 1)
t.Log(s2[0], s2[1], s2[2], s2[3]) //0 0 0 1
}
切片其實(shí)是共享了存儲(chǔ)空間,即如果兩個(gè)切片有重疊元素,那么修改時(shí)會(huì)相互影響。
func TestSliceShare(t *testing.T) {
var s = []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := s[3:6]
s2 := s[5:8]
t.Log(s1, len(s1), cap(s1)) // [4 5 6] 3 6
t.Log(s2, len(s2), cap(s2)) // [6 7 8] 3 4
s2[0] = 100
t.Log(s1) // [4 5 100]
//t.Log(s1 == s2) // 切片不能互相比較,和數(shù)組不同
t.Log(s1 == nil) // 切片只能和nil比較
}
- map,主要注意的是判斷key是否存在。遍歷也是用range得到k,v。
func TestMap(t *testing.T) {
m1 := map[int]int{1: 100, 2: 200, 3: 300}
t.Log(m1[1]) //100
m2 := map[string]string{"name": "eric", "gender": "male"}
t.Logf("len m2 = %d", len(m2)) //len m2 = 2
m3 := map[string]int{}
m3["math"] = 59
m3["language"] = 61
t.Logf("len m3 = %d", len(m3)) //len m3 = 2
m4 := make(map[int]int, 10) // 10是capacity
t.Logf("len m4 = %d", len(m4)) //len m4 = 0
// 如果key不存在則根據(jù)value類型反饋對(duì)應(yīng)的初始化的值,如int的0或string的空字符串
t.Log(m1[4]) // 0
t.Log(m2["age"]) // 空字符串
// 所以無(wú)法判斷原先就是0值和空字符串,go提供了判斷方法
if v, ok := m1[4]; ok {
t.Log("value is %i", v)
} else {
t.Log("key is not existing")
}
// 遍歷map,也是range
for k, v := range m2 {
t.Logf("%s : %s", k, v)
}
}
比較牛的是map的value還可以是函數(shù):
m8 := map[int]func(num int) int{}
m8[1] = func(num int) int { return num }
m8[2] = func(num int) int { return num * num }
m8[3] = func(num int) int { return num * num * num }
t.Log(m8[1](2)) //2
t.Log(m8[2](2)) //4
t.Log(m8[3](2)) //8
go集合沒(méi)有set,可以用map[type]bool來(lái)變相實(shí)現(xiàn)。
m9 := map[int]bool{}
m9[1] = true
t.Log(m9[1] == true) // true
delete(m9, 1)
t.Log(m9[1] == true) // false
- 字符串。
func TestString1(t *testing.T) {
var s1 string
s1 = "hello"
t.Log(len(s1)) // 5
//s1[1] = "k" // 字符串其實(shí)是不可更改的切片
s2 := "\x53\xbb"
t.Log(s2) // 是個(gè)亂碼
t.Log(len(s2)) // 2,不管實(shí)際寫(xiě)的是什么,長(zhǎng)度最終看看byte的長(zhǎng)度
s3 := "中"
t.Log(len(s3))
c := []rune(s3) //3
t.Logf("Unicode:%x", c[0]) //Unicode:4e2d
t.Logf("UTF8:%x", s3) //UTF8:e4b8ad
s4 := "\xe4\xb8\xad"
t.Log(len(s4)) //3
t.Log(s4) //中
// range返回的是rune,但是我們可以通過(guò)格式化輸出進(jìn)行轉(zhuǎn)換成字符或者二進(jìn)制等形式
// 中 4e2d 20013
// 華 534e 21326
// 民 6c11 27665
// 族 65cf 26063
s5 := "中華民族"
for _, c := range s5 {
t.Logf("%[1]c %[1]x %[1]d", c)
}
}
兩個(gè)庫(kù)用戶擴(kuò)展字符串操作的:
import (
"strings"
"strconv"
)
func TestOperateString(t *testing.T) {
s1 := "1,2,3,4"
parts := strings.Split(s1, ",")
for _, part := range parts {
t.Log(part)
}
t.Log(strings.Join(parts, "-")) //1-2-3-4
t.Logf("string is %s", strconv.Itoa(100)) //string is 100
if v, err := strconv.Atoi("100"); err == nil {
t.Logf("result is %d", 100+v) //result is 200
}
}
- 函數(shù),一等公民。
- 可返回多個(gè)值
- 所有參數(shù)都是值傳遞
- 函數(shù)可以作為變量的值,作為參數(shù)值,可以作為返回值
- 如果多個(gè)返回值,不需要多個(gè)時(shí),用下劃線代替,之前有演示過(guò)
func ComputeFunction(op1 int, op2 int) (int, int, int, int) {
return op1 + op2, op1 - op2, op1 * op2, op1 / op2
}
func TestCompute(t *testing.T) {
t.Log(ComputeFunction(3, 5)) //8 -2 15 0
}
再看看函數(shù)作為參數(shù)和返回值的情況:
func timeSpent(innerFunc func(op int) int) func(op int) int {
return func(n int) int {
start := time.Now()
ret := innerFunc(n)
fmt.Println("time spent seconds:", time.Since(start).Seconds())
return ret
}
}
func slowFunc(n int) int {
time.Sleep(time.Second * 2)
return n
}
func TestFunc(t *testing.T) {
resFunc := timeSpent(slowFunc) // 得到了一個(gè)給slowFunc增強(qiáng)的函數(shù),即增加了打印計(jì)時(shí)功能
t.Log(resFunc(100)) // 100,得到的函數(shù)可以直接使用
}
再來(lái)看看可變參數(shù),這個(gè)其他語(yǔ)言也有:
func sum(ops ...int) int {
s := 0
for _, v := range ops {
s += v
}
return s
}
func TestSum(t *testing.T) {
t.Log(sum(1, 2, 3, 4, 5)) //15
t.Log(sum(1, 2, 3)) //6
}
再看看延遲執(zhí)行:
func ClearResource() {
fmt.Println("Clear resource...")//2.不管有無(wú)報(bào)錯(cuò),最后都會(huì)先輸出Clear resource...
}
func TestDeferFunc(t *testing.T) {
defer ClearResource() // defer相當(dāng)于try-catch中的finally,函數(shù)最后會(huì)執(zhí)行它,盡管有panic報(bào)錯(cuò)也會(huì)執(zhí)行它
fmt.Println("Start") // 1.先輸出Start
panic("error")
}
最后補(bǔ)充自定義類型type
,可以簡(jiǎn)化代碼:
// 自定義一個(gè)類型,下面就能用這個(gè),相當(dāng)于定一個(gè)類型別名
type convFunc func(op int) int
func timeSpent(innerFunc convFunc ) convFunc {
return func(n int) int {
start := time.Now()
ret := innerFunc(n)
fmt.Println("time spent seconds:", time.Since(start).Seconds())
return ret
}
}
- go語(yǔ)言不支持繼承(黑魔法寫(xiě)法除外),建議使用復(fù)合。go語(yǔ)言中定義結(jié)構(gòu)和行為如下:
type User struct {
Id string
Name string
Age int
}
func TestStruct(t *testing.T) {
e1 := User{"1", "Eric", 18}
t.Log(e1) //{1 Eric 18}
e2 := User{Id: "2", Name: "Tom"}
t.Log(e2.Name) //Tom
e3 := new(User) // 這種其實(shí)返回的是一個(gè)引用/指針
e3.Id = "3"
e3.Name = "Jerry"
t.Log(e3.Age) //0
t.Logf("%T", e1) //test.User
t.Logf("%T", e3) //*test.User
}
定義行為和普通函數(shù)不太一樣,需要注意一下,建議使用引用方式,即結(jié)構(gòu)體參數(shù)使用user *User
之類的格式:
unc (user User) StringFmt1() string {
fmt.Printf("StringFmt1:The Name Address is %x\n", unsafe.Pointer(&user.Name))
return fmt.Sprintf("%s-%s-%d", user.Id, user.Name, user.Age)
}
// 建議使用這種引用方法,避免復(fù)制產(chǎn)生的消耗
func (user *User) StringFmt2() string {
fmt.Printf("StringFmt2: The Name Address is %x\n", unsafe.Pointer(&user.Name))
return fmt.Sprintf("%s/%s/%d", user.Id, user.Name, user.Age)
}
// 這是普通函數(shù)
func StringFmt3(user User) string {
fmt.Printf("StringFmt3: The Name Address is %x\n", unsafe.Pointer(&user.Name))
return fmt.Sprintf("%s/%s/%d", user.Id, user.Name, user.Age)
}
func TestStructFunc(t *testing.T) {
u := User{Id: "100", Name: "Robin", Age: 99}
fmt.Printf("The Name Address is %x\n", unsafe.Pointer(&u.Name)) //The Name Address is c000114910
t.Log(u.StringFmt1()) //StringFmt1:The Name Address is c000114940
t.Log(u.StringFmt2()) //StringFmt2: The Name Address is c000114910
t.Log(StringFmt3(u)) //StringFmt3: The Name Address is c0001149a0
}
- go的接口比較解耦,不會(huì)強(qiáng)相互依賴,這可以從下面的例子看出來(lái)。下面的例子先寫(xiě)了一個(gè)普通的結(jié)構(gòu)體并定義了一個(gè)行為,這時(shí)候沒(méi)有任何接口。如果我們?cè)敢獾脑?,可以把這個(gè)行為單獨(dú)提取出來(lái)形成一個(gè)接口。這個(gè)過(guò)程我們發(fā)現(xiàn),提取接口完全不需要修改原先行為的代碼和使用。只需要方法簽名一致就行。
type Employee struct {
Age int
}
func (e *Employee) AgeAddOneYear() int {
return e.Age + 1
}
func TestInterface(t *testing.T) {
e := Employee{Age: 18}
t.Log(e.AgeAddOneYear()) // 19
}
然后在文件任何位置提取一個(gè)接口,保證簽名一致即可。原先代碼正常運(yùn)行。我們稱這種接口為沒(méi)有侵入性。我們的實(shí)現(xiàn)也不依賴這個(gè)接口定義,即沒(méi)有這個(gè)接口的時(shí)候我們也能正常運(yùn)行使用。
type MyInterface interface {
AgeAddOneYear() int
}
- 復(fù)合。
- go有沒(méi)有繼承,只要使用父類去定義子類即可發(fā)現(xiàn)
var pet Pet := new(Dog)
是不可以的,連強(qiáng)制轉(zhuǎn)換也是不行的。 - 所以一個(gè)變量在初始化的時(shí)候就決定了它的類型是Pet還是Dog,那么它接下來(lái)的行為就好說(shuō)了:以為Dog復(fù)合了Pet的數(shù)據(jù)和行為,如果Dog自己有就調(diào)用自己的,自己沒(méi)有就調(diào)用Pet的。所以下面的
func (d *Dog) SpeakTo(host string)
是關(guān)鍵。
type Pet struct{}
type Dog struct {
Pet
}
func (p *Pet) Speak() {
fmt.Printf("Pet is speaking")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Print(":", host)
}
func (d *Dog) Speak() {
fmt.Printf("Dog is speaking")
}
// 如果沒(méi)有這個(gè)行為,則輸出的是Pet的行為,因?yàn)閺?fù)合了Pet的代碼:Pet is speaking:Eric
// 如果有這個(gè)行為,則輸出的是Dog的行為:Dog is speaking:Eric
func (d *Dog) SpeakTo(host string) {
d.Speak()
fmt.Print(":", host)
}
func TestInherit(t *testing.T) {
d := new(Dog)
d.SpeakTo("Eric")
}
- 多態(tài)的實(shí)現(xiàn),主要在于那個(gè)方法的參數(shù)使用接口參數(shù)。
type Duck struct{}
type Goose struct{}
func (d *Duck) bark() string {
return "gua gua gua..."
}
func (g *Goose) bark() string {
return "e e e..."
}
// 這個(gè)方法是關(guān)鍵,使用了接口作為參數(shù),這樣就能實(shí)現(xiàn)多態(tài)
func diffrentbark(interf MyInterface2) string {
return interf.bark()
}
func TestBark(t *testing.T) {
duck := new(Duck)
t.Log(diffrentbark(duck)) //gua gua gua...
goose := new(Goose)
t.Log(diffrentbark(goose)) //e e e...
}
- 接口的常見(jiàn)使用方法。
- 按照上述的多態(tài)的情況,定義函數(shù)的時(shí)候參數(shù)我們經(jīng)常用接口,但更常用的是我們使用空接口
- 配合斷言判斷不同情況
- 好的習(xí)慣是讓接口盡量小,一般包含一個(gè)方法,如果需要大的接口則使用小接口組合而成
// 注意這里的參數(shù),是一個(gè)空接口,然后在代碼里判斷這個(gè)接口實(shí)際傳過(guò)來(lái)的可能的值或者類型,做相應(yīng)的行為
func UseEmptyInterface(p interface{}) {
// if v, ok := p.(int); ok {
// fmt.Println("int is", v)
// } else if v, ok := p.(string); ok {
// fmt.Println("string is", v)
// }
switch v := p.(type) {
case int:
fmt.Println("int is", v)
case string:
fmt.Println("string is", v)
default:
fmt.Println("unknown")
}
}
func TestEmptyInterface(t *testing.T) {
UseEmptyInterface(100) //int is 100
UseEmptyInterface("100") //string is 100
}
- 錯(cuò)誤的處理方法,正常情況下使用
errors.New("")
即可。如果錯(cuò)誤類型比較多,可以定義錯(cuò)誤類型var xxx = errors.New()
。這里需要注意的是,要習(xí)慣于go這種返回多個(gè)值得形式,并利用多個(gè)參數(shù)這個(gè)將返回值和提示信息一并返回。
var LessThanTwo = errors.New("The num should not less that 2.")
func GetFib(n int) ([]int, error) {
if n < 2 {
return nil, LessThanTwo
}
if n > 100 {
return nil, errors.New("The num should not more than 100.")
}
fibList := []int{1, 1}
for i := 2; i < n; i++ {
fibList = append(fibList, fibList[i-2], fibList[i-1])
}
return fibList, nil
}
func TestError(t *testing.T) {
if lst, err := GetFib(2); err != nil {
t.Log(err)
} else {
t.Log(lst)
}
}
看一下recover的使用方法,配合defer使用,可以獲得錯(cuò)誤信息,在這里釋放資源或者做一些其他操作:
func TestRecover(t *testing.T) {
defer func() {
if err := recover(); err != nil {
t.Log("recover from error:", err) //recover from error: crashed...
}
}()
t.Log("start...")
panic(errors.New("crashed..."))
}
需要注意的是如果使用os.Exist(0)
退出的話,是不會(huì)執(zhí)行defer
的。
- 包的引用方式不同版本的go不一樣,早期的go項(xiàng)目通過(guò)設(shè)置GOPATH,所有項(xiàng)目共用一套GOOATH以及包版本,所有的代碼寫(xiě)在src文件夾中。后來(lái)有
go mod
的模塊管理工具,每個(gè)項(xiàng)目都可以寫(xiě)在不同位置,只要先使用go mod init projectName
初始化項(xiàng)目即可。后面就可以互相使用本地包,從項(xiàng)目名開(kāi)始寫(xiě),比如下面初始化了一個(gè)goProjects
項(xiàng)目。
第三方包的引用和使用:
- 先下載
- 后引用
// 去官網(wǎng)查詢包https://pkg.go.dev
// go get下載 -u強(qiáng)制更新
go get -u github.com/google/go-cmp/cmp
// 在項(xiàng)目中使用
import (
"testing"
cmp "github.com/google/go-cmp/cmp"
)
func TestImportThirdPackage(t *testing.T) {
t.Log(cmp.Equal(1, 2)) // false
}
這個(gè)時(shí)候項(xiàng)目中有2個(gè)文件:
- go.mod,里面記錄了依賴版本的全部信息
- go.sum,記錄了所有依賴module的校驗(yàn)信息
注意一些歷史小知識(shí):最早的沒(méi)有模塊管理和包管理,后來(lái)有了vendor+利用第三方glide或dep來(lái)管理,它們會(huì)生成一個(gè)配置文件,在這個(gè)配置文件里記錄了項(xiàng)目使用的依賴包信息。再后來(lái)官方退出了module功能。這就是簡(jiǎn)單的go項(xiàng)目管理和依賴管理的發(fā)展。
- 協(xié)程。關(guān)于goroutine協(xié)程們可以參考這邊文章GMP 原理與調(diào)度。原先只有線程M和協(xié)程G,雖然實(shí)現(xiàn)了M對(duì)N的關(guān)系。但是仍然存在問(wèn)題,后來(lái)增加了處理器P即調(diào)度器。
在 Go 中,線程是運(yùn)行 goroutine 的實(shí)體,調(diào)度器的功能是把可運(yùn)行的 goroutine 分配到工作線程上。
1、全局隊(duì)列(Global Queue):存放等待運(yùn)行的 G。
2、P 的本地隊(duì)列:同全局隊(duì)列類似,存放的也是等待運(yùn)行的 G,存的數(shù)量有限,不超過(guò) 256 個(gè)。新建 G’時(shí),G’優(yōu)先加入到 P 的本> 地隊(duì)列,如果隊(duì)列滿了,則會(huì)把本地隊(duì)列中一半的 G 移動(dòng)到全局隊(duì)列。
3、P 列表:所有的 P 都在程序啟動(dòng)時(shí)創(chuàng)建,并保存在數(shù)組中,最多有 GOMAXPROCS(可配置) 個(gè)。
4、M:線程想運(yùn)行任務(wù)就得獲取 P,從 P 的本地隊(duì)列獲取 G,P 隊(duì)列為空時(shí),M 也會(huì)嘗試從全局隊(duì)列拿一批 G 放到 P 的本地隊(duì)> 列,或從其他 P 的本地隊(duì)列偷一半放到自己 P 的本地隊(duì)列。M 運(yùn)行 G,G 執(zhí)行之后,M 會(huì)從 P 獲取下一個(gè) G,不斷重復(fù)下去。
以下的協(xié)程代碼,在函數(shù)前加go
就進(jìn)入?yún)f(xié)程了。
func TestRoutine(t *testing.T) {
for i := 0; i < 10; i++ {
go func(num int) {
fmt.Println(num)
}(i)
}
time.Sleep(time.Second * 1)
}
- 緊接著就是不同協(xié)程之間的數(shù)據(jù)安全,即鎖。還有waitgroup。
// 沒(méi)有鎖
func TestNotLock(t *testing.T) {
counter := 0
for i := 0; i < 5000; i++ {
go func() {
counter++
}()
}
time.Sleep(time.Second * 2)
fmt.Println(counter)
}
// 有鎖,注意defer中釋放
func TestLock(t *testing.T) {
var mut sync.Mutex
counter := 0
for i := 0; i < 5000; i++ {
go func() {
defer func() {
mut.Unlock()
}()
mut.Lock()
counter++
}()
}
time.Sleep(time.Second * 2)
fmt.Println(counter)
}
waitgroup使用wait的時(shí)候就是等所有的都執(zhí)行完,所有指的多少個(gè),就是add的數(shù)量。使用done相當(dāng)于減1。
func TestLockWaitGroup(t *testing.T) {
var mut sync.Mutex
var wg sync.WaitGroup
counter := 0
for i := 0; i < 5000; i++ {
wg.Add(1)
go func() {
defer func() {
mut.Unlock()
wg.Done()
}()
mut.Lock()
counter++
}()
}
wg.Wait()
fmt.Println(counter)
}
- csp并發(fā)機(jī)制:channel。分為兩種一種是一對(duì)一交換數(shù)據(jù),只有雙方發(fā)送和接收都完成了才能往下進(jìn)行否則阻塞,即這個(gè)指需要從channel中拿走才行。第二種是給channel加buffer,即發(fā)送和接收解耦,不會(huì)阻塞。注意channel的符號(hào)
<-
往channel中放數(shù)據(jù)和取數(shù)據(jù)。
func doTask() chan string {
// 如果使用這種一對(duì)一的channel,那么只有當(dāng)fmt.Println(<-retCh)執(zhí)行時(shí)相當(dāng)于接收數(shù)據(jù)后,這個(gè)retCh <- "init data..."才取消阻塞繼續(xù)執(zhí)行
// 所以fmt.Println("doing task done")總是在fmt.Println(<-retCh)后執(zhí)行
//retCh := make(chan string)
// 如果使用這種channel,1就是buffer,這時(shí)候不用阻塞,retCh <- "init data..."執(zhí)行完直接執(zhí)行fmt.Println("doing task done"),不用管最后fmt.Println(<-retCh)何時(shí)從channel中接收數(shù)據(jù)
retCh := make(chan string, 1)
go func() {
fmt.Println("doing task")
retCh <- "init data..."
fmt.Println("doing task done")
}()
return retCh
}
func doOtherTask() {
fmt.Println("doing other task")
time.Sleep(time.Second * 3)
fmt.Println("doing other task is done")
}
func TestAsyncChannel(t *testing.T) {
retCh := doTask()
doOtherTask()
fmt.Println(<-retCh)
}
另外有一個(gè)select多路選擇器,了解一下,類似于switch,可以從不同channel中獲取結(jié)果進(jìn)行處理:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-678526.html
func TestSelect(t *testing.T) {
select {
case retCh := <-doTask():
fmt.Print(retCh)
case <-time.After(time.Second * 1):
fmt.Print("time out")
}
}
channel還有一個(gè)close方法。假如有個(gè)生產(chǎn)者和消費(fèi)者,因?yàn)橄M(fèi)者不知道生產(chǎn)者生產(chǎn)數(shù)量,所以我們用無(wú)限for循環(huán),但如果一直沒(méi)有生產(chǎn),那么就會(huì)一直阻塞在消費(fèi)者獲取數(shù)據(jù)處,如果生產(chǎn)結(jié)束時(shí)給一個(gè)關(guān)閉通道的信號(hào),消費(fèi)者判斷是否已經(jīng)傳送結(jié)束,那么就能針對(duì)性處理。以下就是通過(guò)取值時(shí)不僅可以取值還能獲取狀態(tài),如果是true
就正常,如果是false
意味著關(guān)閉了:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-678526.html
- 如果通道已關(guān)閉,無(wú)法傳數(shù)據(jù),會(huì)報(bào)錯(cuò)
- 如果通道關(guān)閉,則會(huì)喚醒所有的訂閱者,并且傳狀態(tài)為false,可以用于信號(hào)訂閱發(fā)布
上面說(shuō)的利用這個(gè)close
實(shí)現(xiàn)訂閱發(fā)布比如關(guān)閉所有協(xié)程。
// 比如我們這邊判斷要不要關(guān)閉協(xié)程,則可以用
func isCanceled(ch chan struct{}) bool {
select {
// 只要收到消息,就會(huì)走case <-ch,當(dāng)然這個(gè)消息我們一般用于關(guān)閉
case <-ch:
return true
default:
return false
}
}
// 此時(shí),在程序中使用channel,有需要時(shí)就關(guān)閉這個(gè)channel,在需要用到isCanceled判斷的地方就能判斷已關(guān)閉,然后做相應(yīng)操作
- context和取消。如果遇到一個(gè)嵌套的任務(wù),取消一個(gè)節(jié)點(diǎn)時(shí)需要取消其子節(jié)點(diǎn),就可以用context實(shí)現(xiàn)。
func isCanceled(ctx context.Context) bool {
select {
case <-ctx.Done():
return true
default:
return false
}
}
func TestContextCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
for i := 0; i < 5; i++ {
go func(i int, ctx context.Context) {
for {
if isCanceled(ctx) {
break
}
time.Sleep(time.Millisecond * 10)
}
fmt.Println(i, "canceled")
}(i, ctx)
}
cancel()
time.Sleep(time.Second * 1)
}
// 輸出結(jié)果:
4 canceled
1 canceled
3 canceled
2 canceled
0 canceled
- 還是sync下的一個(gè)Once,類似懶漢模式,只運(yùn)行一次的功能。
type Singleton struct{}
var once sync.Once
var singleInstance *Singleton
func GetSingleton() *Singleton {
// 這里代碼調(diào)用多次,只執(zhí)行一次
once.Do(func() {
fmt.Println("create instance ...")
singleInstance = new(Singleton)
})
return singleInstance
}
func TestSyncOnce(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
obj := GetSingleton()
fmt.Printf("obj is %x\n", unsafe.Pointer(obj))
wg.Done()
}()
}
wg.Wait()
}
// 輸出
create instance ...
obj is ec2ba0
obj is ec2ba0
obj is ec2ba0
obj is ec2ba0
obj is ec2ba0
到了這里,關(guān)于Go語(yǔ)言入門記錄:從基礎(chǔ)到變量、函數(shù)、控制語(yǔ)句、包引用、interface、panic、go協(xié)程、Channel、sync下的waitGroup和Once等的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!