惊鸿一瞥 Hello,World 1 2 3 4 5 6 7 8 9 package main import { "fmt" } func main () { fmt.Println("Hello, World!" ) }
package关键字声明代码所属的包,本例中包名为main。 import关键字导入需要的包 func关键字声明函数 当运行一个Go程序,总是从main包的main函数开始
fmt包:format之缩写,提供格式化输入输出函数
此外,大括号有其唯一的制定格式
基本知识 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" func main () { const ( days = 365 name = "days" ) var ( dream = "I have a dream" new_dream = "I had a dream" ) fmt.Println("One year has" ,days,name) fmt.Print("One year has " ) fmt.Print(days) fmt.Print(" days\n" ) fmt.Printf("One year has %d days\n" ,days) fmt.Println(dream,"\n" ,new_dream) }
Println会自动补换行符
Go中运算符和C中大抵相同,但没有++count这种前置增量操作
基本控制结构 在go中,true是唯一真值,false是唯一假值
比较运算符:==,!=,<=,<,>=,>
Go不允许文本和数值直接比较
1 2 3 4 5 6 7 8 9 10 func demo () { if 判断语句{ } else if 判断语句{ } else 判断语句{ }
逻辑与:&& 逻辑或:|| 逻辑非:!
Switch实现分支判断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" func main () { const days = 365 switch days { case 100 : fmt.Println(100 ,"days" ) case 200 ,365 : fmt.Println("hhhh" ) fallthrough default : fmt.Println("23333333" ) } }
for实现循环结构 关于range关键字:
1 2 3 4 5 for index,value := range array_cut{}
作用域 go作用域:以大括号分界 虽然没有用到大括号,但default和case都i引入了新的作用域
简短声明:
1 2 3 var name = 'ubuntu' name := 'ubuntu'
简短声明作用: 以下三种情况,变量作用域都在大括号内 这种使用方法无法用var声明替代
1 2 3 4 5 6 7 8 9 10 11 for count := 10 ; count > 0 ;count--{ } if count := rand.Intn(3 ); count == 0 { } switch num := rand(10 ); num{}
类型 浮点数 1 2 3 4 5 6 7 8 9 10 age := 19.2 var age =19.2 var age float64 = 19.2 var age float32 = 19.2 var age
整数 1 2 3 4 5 6 year := 2021 var year = 2021 var year int 2021 var distance int64 = 2.33e12
big包 big包提供3种类型:
存储大整数big.Int
存储任意精度浮点数big.Float
存储分数big.Rat
1 2 3 4 5 6 7 8 9 10 hhhhh := big.NewInt(2333333 ) hhhhhhh := new (big.Int) hhhhhhh.SetString("23333333333333333" ,10 ) const distance = 23333333333333333333333333
多语言文本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 peace := "peace" var peace = "peace" var peace string = "peace" var peace string var peace 'hhhhhhh\n' var peace 'hhhhh hhhhhhhh hhhhh'
1 2 3 4 5 6 hhhh = 'laugh' h = laugh[4 ] laugh[4 ] = 'd'
类型转换 类型转换: 转换一个变量,只需用与目标类型同名的函数来包裹该变量
1 2 3 4 5 6 7 8 hhhh = 'laugh ' + 'is the power of birth.' hhh ='laugh ' + 'is the power of birth.' + 10 hhh = 'laugh ' + 'is the power of birth.' + '10'
这部分与c语言大致相同 浮点数转换为整数,没有任何舍入,而是直接截断
关于字符串转换
1 2 3 4 5 6 7 8 9 10 11 12 fmt.Println(string (960 ),string (940 ),string (969 ),string (33 )) hhh ='laugh ' + 'is the power of birth.' + strconv.Itoa(10 ) const hhh string = '23333' number := strconv.Atoi(hhh)
代码点: 一个数字对应一个特殊字符 如:π ά ω !分别对应960 940 969 33
构建块 函数 基本格式:
func function_name( [parameter list] ) [return_types] { 函数体 }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func Add (number1 int ,number2 int ) (sum int ) { sum = number1 + number2 return sum } func Add (number1 int ,number2 int ) (sum int ) { sum = number1 + number2 return } func Calculate (number1 ,number2 int ) (sum,multitude int ) { sum = number1 + number2 multitude = number2*number1 return sum,multitude } func Calculate (number1 ,number2 int ) (sum,multitude int ) { sum = number1 + number2 multitude = number2*number1 return
方法 声明新类型: 格式:
注意:
1 2 3 4 5 6 7 8 9 10 type type1 float64 type type2 float64 var number1 type1 := 10 var number2 type2 := 15 var number3 float64 = 20 numebr1 = number2 number2 = number3
在go语言中,没有类和对象 格式:
1 2 3 func (接受者) 方法名(形参列表) (返回值列表) {}
接受者是某种非内置类型的变量 用于建立类型和方法之间的关联
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" type myInt int func (number1 myInt) calculate (number2 int ) (sum int ,multitude int ) { return int (number1)+number2,int (number1)*number2 } func main () { var number1 myInt = 10 var number2 =20 var sum int var multitude int sum,multitude = number1.calculate(number2) fmt.Println(sum,multitude) }
一等函数 在go中,函数是 一等 值,可以把函数赋值给变量,可以把函数传递给函数,也可以编写创建并返回函数的函数。
将函数赋值给变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package mainimport "fmt" type myInt int64 func Add (number1,number2 int ) myInt { return myInt(number1+number2) } func Multitude (number1,number2 int ) myInt { return myInt(number1*number2) } func main () { func1 := Add var func2 func (int ,int ) myInt = Multitude var number1 int = 10 var number2 int = 20 var result myInt result = func1(number1,number2) fmt.Println(result) result = func2(number1,number2) fmt.Println(result) func1 = func2 result = func1(number1,number2) fmt.Println(result) func2 = Add result = func2(number1,number2) fmt.Println(result) }
将函数传递给其他函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type myInt int64 type myFunc func (int ,int ) myInt func Add (number1,number2 int ) myInt { return myInt(number1+number2) } func Multitude (number1,number2 int ) myInt { return myInt(number1*number2) } func Add_Multitude (number1,number2 int , func1,func2 myFunc) (sum,multitude myInt) { sum = func1(number1,number2) multitude = func2(number1,number2) return sum,multitude }
闭包和匿名函数 匿名函数在Go中也称为函数字面量
闭包函数:声明在一个函数内部的函数 闭包:内部函数总是可以访问其所在外部函数中的变量,即使它所在的外部函数已返回(寿命终结)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport "fmt" type myFunc func () int func func1 (func2 myFunc,offset int ) myFunc { return func () int { return func2() + offset } } func func2 () int { return 10 } func main () { func4 := func (message string ) { fmt.Println(message) } func4("hhhhhhhhhhhhhhhhhhhhhhh" ) func () { fmt.Println("hhhhhhhhh" ) } () func3 := func1(func2,20 ) fmt.Println(func3()) }
可变参数函数 声明放在函数形参的最后一位 可以传入多个参数或不传参数
1 2 3 4 5 6 7 func f (name...string ) {} cut_test := []string {"sss" ,"hhh" ,"2333" } cut = f(cut_test..)
收集器 数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var planests [8 ]string planest[0 ] = "Mercury" mercury := planets[0 ] fmt.Println(len (planest)) var numbers = [5 ]int {1 ,2 ,3 }var numbers = [...]int {1 ,2 ,3 ,}
数组用于赋值或传递给参数时会产生一个副本 故函数内部对数组的修改不会影响原数组
数组嵌套定义即形成所谓的多维数组
切片
切片数组不会导致数组被修改,只是创建了指向数组的视图,把该视图称为切片类型
格外注意的是,切片或切片的切片都是数组的视图,对他们进行修改会导致原数组的修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package mainimport ( "fmt" "sort" ) func main () { numbers := [5 ]int {1 ,2 ,3 ,4 ,5 } cut1 := numbers[0 :3 ] cut2 := numbers[0 :2 ] fmt.Println(numbers,cut1,cut2) cut2[1 ] = 100 fmt.Println(numbers) name := "yangzi" name1 := name[:4 ] fmt.Println(name,name1) name = "hhhhh" fmt.Println(name,name1) name1 = "233333" fmt.Println(name,name1) numbers3 := []string {"11" ,"40" ,"ann" ,"ccc" ,"bbbb" } sort.StringSlice(numbers3).Sort() fmt.Println(numbers3) }
关于sort: 标准库sort包声明了一种StringSlice类型:
1 type StringSlice []string
该类型还有关联的Sort方法:
1 func (p StringSlice) Sort ()
Go语言通过切片和append函数来实现动态数组的功能
切片长度:切片中可见元素的数量 切片容量:切片的底层数组决定了切片的容量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package mainimport "fmt" func main () { vocabulary := []string {"terminally" ,"visible" ,"lethal" ,"legally" ,"prescribe" } vocabulary = append (vocabulary,"partnership" ,"compassion" ,"publication" ) arry_vocabulary := [...]string {"candidness" ,"dose" ,"medication" ,"constitutionally" ,"uphold" ,"legislation" ,} cut_vocabulary := arry_vocabulary[0 :4 :4 ] fmt.Println(cut_vocabulary) cut_vocabulary = append (cut_vocabulary,"spark" ) fmt.Println(cut_vocabulary) fmt.Println(arry_vocabulary) cut_vocabulary2 := arry_vocabulary[0 :4 ] cut_vocabulary2 = append (arry_vocabulary[:],"notable" ) fmt.Println(cut_vocabulary2) fmt.Println(arry_vocabulary) cut_vocabulary3 := arry_vocabulary[0 :4 :5 ] cut_vocabulary3 = append (cut_vocabulary,"spark" ) fmt.Println(cut_vocabulary3) fmt.Println(arry_vocabulary) test_cut1 := make ([]string ,0 ,10 ) test_cut1 = append (test_cut1,"hhhhhhhhhhhhhh" ) test_cut2 := make ([]string ,10 ) test_cut2 = append (test_cut2,"hhhhhhhhh" ) }
映射 映射(map):可将键映射到值 与数组和切片使用序列整数作为索引不同的是,映射的键几乎可以是任何类型
可以理解为Python中的字典
Go语言必须为映射的键和值指定类型 格式:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package mainimport "fmt" func main () { map1 := map [string ]int { "key1" :1 , "key2" :2 , } map2 := make (map [string ]int ) map2["New_York" ] = 1 map2["San_Franscisco" ] = 2 map2["Tokyo" ] = 3 map3 := map1 map3["key1" ]=2 fmt.Println(map3) fmt.Println(map1) for key := range map2{ fmt.Print(key) } fmt.Print("\n" ) for _,value := range map2{ fmt.Print(value) } fmt.Print("\n" ) for key,value := range map2{ fmt.Println(key,value) } delete (map2,"New_York" ) fmt.Println(map2["New_York" ]) map2["Shenzheng" ] = 0 if value,ok := map2["Shenzheng" ]; ok{ fmt.Println(value," is exited." ) }else { fmt.Println("not exited" ) } }
状态与行为 状态:可以是值或结构package main 行为:函数和方法
结构 基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package mainimport "fmt" type city struct { country,name string rank int } type city struct { country,name string rank int } func main () { var newYork city newYork.rank = 1 newYork.name = "New York" newYork.country = "American" var tokyo = city{country:"Japan" ,rank:2 } fmt.Println(tokyo.name) tokyo.name = "Tokyo" anotherTokyo := tokyo anotherTokyo.rank = 3 fmt.Println(anotherTokyo,"\n" ,tokyo) cities := []city{newYork,tokyo} fmt.Println(cities) hhhh := struct { ccc int ddd string }{2333333 ,"2333333333" } fmt.Println(hhhh) }
Go面向对象 Go没有类,而是通过结构与方法的结合实现面向对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package maintype literallyPopulation struct { literalRate float64 totalPopulation int } type country struct { countryName string theLiterallyPopulation literallyPopulation } func newCountry (countryName string , literalRate float64 ,totalPopulation int ) (c country) { l := literallyPopulation{literalRate:literalRate,totalPopulation:totalPopulation} c = country{countryName:countryName,theLiterallyPopulation: l} return c } func (theLiteralPopulation literallyPopulation) countLiteralPopulation () (float64 ) { return theLiteralPopulation.literalRate*float64 (theLiteralPopulation.totalPopulation) } func (c country) countLiteralPopulation () float64 { return c.theLiterallyPopulation.literalRate*float64 (c.theLiterallyPopulation.totalPopulation) } func main () {c := newCountry("hhhh" ,0.34 ,10000 ) lp := c.countLiteralPopulation() fmt.Println(lp) }
实现自动转发:
接口 用于实现多态
接口是一种类型,是一组method的集合 接口命名一般以-er结尾
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 package mainimport "fmt" type shower interface { showName() string showRank() int } type sayer interface { sayCharacteristic() string } type sayAndShower interface { sayer shower } type city struct { name string rank int characteristic string } type country struct { name string rank int characteristic string } func (city city) showName () string { return city.name } func (city city) showRank () int { return city.rank } func (city city) sayCharacteristic () string { return city.characteristic } func (country country) showName () string { return country.name } func (country country) showRank () int { return country.rank } func (country country) sayCharacteristic () string { return country.characteristic } func main () { structCity := city{characteristic: "very big" , rank: 23333 , name: "city of night" } structCountry := country{characteristic: "nb" , rank: 100 , name: "nbcountry" } var show shower show = structCity fmt.Println(show.showRank()) fmt.Println(show.showName()) show = structCountry fmt.Println(show.showRank()) fmt.Println(show.showName()) say := structCity fmt.Println(say.sayCharacteristic()) say = structCountry fmt.Println(say.sayCharacteristic()) var showAndSay sayAndShower showAndSay = structCountry fmt.Println(showAndSay.showRank(), showAndSay.showName(), showAndSay.sayCharacteristic()) }
指针 基本使用 几点需要注意的:
对于指向结构的指针,Go中用.即可完成对方法和字段的访问,且解引用不是必须的
Go中数组与指针是两种独立的类型
对于指向数组的指针,Go语言提供了自动解引用的特性,即解引用不是必须的
Go语言没有为切片和映射提供了自动解引用的特性
映射是一种隐式指针,所以是指针指向映射的举动往往是多此一举
切片实际上是指向数组的指针
指向切片的指针的作用是修改切片本身,包括切片的长度,容量和起始偏移量
方法的接收者和函数形参在处理指针方面十分类似,都可以实现对原有字段的改变
关于指针和接口:指针接收者能仅能接收指针变量,而值接收者可接受指针或非指针变量
nil nil是指针,切片,映射和接口的零值 解引用nil指针将引发恐慌(panic)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package maintype person struct { score int } func (p *person) getScore () { p.score++ } func main () { var p *person p.getScore() }
1 2 var fn func (a,b int ) int fn(1 ,2 )
nil与切片: 不包含任何元素的空切片和nil切片并不相等,但通常可以替换使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package mainimport "fmt" func main () { var slice []int for _, ingredient := range slice { fmt.Println(ingredient) } fmt.Println(len (slice)) slice = append (slice, 1 , 2 , 3 ) fmt.Println(slice) for _, ingredient := range slice { fmt.Println(ingredient) } fmt.Println(len (slice)) slice = append (slice, 1 , 2 , 3 ) fmt.Println(slice) }
nil与映射 映射在声明之后若没有使用复合字面量或内置make函数初始化,则其值为默认的nil。 可以对值为nil的map进行读取操作,但不可进行写入操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" func main () { var map1 map [string ]int value, ok := map1["hhhh" ] if ok { fmt.Println(value) } for key, value := range map1 { fmt.Println(key, value) } }
nil与接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package mainimport ( "fmt" ) func show (x interface {}) { fmt.Printf("Type:%T Value:%v \n" ,x,x) } func main () { var x interface {} i:= 100 j:= "hhhhhh" k:= true x = i fmt.Printf("Type:%T Value:%v \n" ,x,x) x=j fmt.Printf("Type:%T Value:%v \n" ,x,x) x=k fmt.Printf("Type:%T Value:%v \n" ,x,x) show(i) show(j) show(k) var stuInfo = make (map [string ]interface {}) stuInfo["name" ] = "bai" stuInfo["age" ] = 12 stuInfo["sex" ] = "nan" }
并发 扫一下盲 Go中,独立运行的任务谓之goroutine goroutine之间的通信依赖通道 通道(channel)是一种类型
A.进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。 B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。 C.一个进程可以创建和撤销多个线程;同一个进程中的多个线程之间可以并发执行。
并发:多线程程序在一个核的cpu上运行 并行:多线程程序在多个核的cpu上运行
协程:比线程更轻量级的存在,一个线程可拥有多个协程。协程不是被操作系统内核所管理的,而是完全由程序所控制,也就是在用户态执行。这样带来的好处是性能大幅度的提升,因为不会像线程切换那样消耗资源。(线程是由操作系统内核管理的)
协程可以理解为一个特殊的函数
一个线程的多个协程的运行是串行的。当一个协程运行时,其它协程必须挂起。
上下文切换
进程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户是无感知的。进程的切换内容包括页全局目录、内核栈、硬件上下文,切换内容保存在内存中。进程切换过程是由“用户态到内核态到用户态”的方式,切换效率低。
线程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户无感知。线程的切换内容包括内核栈和硬件上下文。线程切换内容保存在内核栈中。线程切换过程是由“用户态到内核态到用户态”, 切换效率中等。
协程的切换者是用户(编程者或应用程序),切换时机是用户自己的程序所决定的。协程的切换内容是硬件上下文,切换内存保存在用户自己的变量(用户栈或堆)中。协程的切换过程只有用户态,即没有陷入内核态,因此切换效率高。
python两种并发:1,多线程和多进程来进行并发;2,使用协程进程并发
python大部分库基于前一种并发方式,而go则完全基于后一种来开发
sync.WaitGroup
方法名
功能
(wg * WaitGroup) Add(delta int)
计数器+delta
(wg *WaitGroup) Done()
计数器-1
(wg *WaitGroup) Wait()
阻塞直到计数器变为0
例如当我们启动了N 个并发任务时,就将计数器值增加N。每个任务完成时通过调用Done()方法将计数器减1。通过调用Wait()来等待并发任务执行完,当计数器值为0时,表示所有并发任务已经完成。
goroutine 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport ( "fmt" "sync" ) var wg sync.WaitGroupfunc sleepyGopher (i int ) { defer wg.Done() fmt.Println(i, "awake" ) } func main () { for i := 0 ; i < 10 ; i++ { wg.Add(1 ) go sleepyGopher(i) } wg.Wait() }
使用互斥锁同步协程
最后得到的结果与预期不相符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var x int64 var wg sync.WaitGroupfunc add () { for i := 0 ; i < 5000 ; i++ { x = x + 1 } wg.Done() } func main () { wg.Add(2 ) go add() go add() wg.Wait() fmt.Println(x) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package mainimport ( "fmt" "sync" ) var x int64 var wg sync.WaitGroupvar lock sync.Mutexfunc add () { for i := 0 ; i < 5000 ; i++ { lock.Lock() x = x + 1 lock.Unlock() } wg.Done() } func sub () { for i := 0 ; i < 5000 ; i++ { lock.Lock() x = x - 1 lock.Unlock() } wg.Done() } func main () { wg.Add(2 ) go add() go sub() wg.Wait() fmt.Println(x) }
能不用锁就不用锁,因为开锁解锁消耗时间,影响性能
读写锁 对于绝大多数web系统,读多写少
我们必须要在读和写上加锁,但当读的操作很多时,一个读的操作上锁对其它读的操作影响不容小觑。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package mainimport ( "fmt" "sync" "time" ) var wg sync.WaitGroupvar lock sync.Mutexfunc read () { defer wg.Done() lock.Lock() fmt.Println("读" ) time.Sleep(time.Second * 1 ) fmt.Println("读毕" ) lock.UnLock() } func main () { for i := 0 ; i < 1000 ; i++{ wg.Add(1 ) go read() } wg.Wait() }
此时,我们需要一把读之间不会相互竞争,而读写之间会相互竞争的锁。
引入读写锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package mainimport ( "fmt" "sync" "time" ) var wg sync.WaitGroupvar lock sync.RWMutexfunc read () { defer wg.Done() lock.RLock() fmt.Println("读" ) time.Sleep(time.Second * 1 ) fmt.Println("读毕" ) lock.RUnlock() } func write () { defer wg.Done() lock.Lock() fmt.Println("写" ) time.Sleep(time.Second * 10 ) fmt.Println("写毕" ) lock.Unlock() } func main () { for i := 0 ; i < 5 ; i++ { wg.Add(1 ) go read() } wg.Add(1 ) go write() wg.Wait() }
协程间的通信机制 channel提供了一种定向通信机制(python和java常用消息队列实现)
分类:有无缓冲;双向还是单向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package mainimport ( "fmt" "sync" ) var wg sync.WaitGroupvar lock sync.Mutexfunc consumer (data chan int ) { defer wg.Done() lock.Lock() for msg := range data { fmt.Println(msg) } lock.Unlock() } func main () { var msg chan int msg = make (chan int , 1 ) msg <- 1 wg.Add(1 ) go consumer(msg) msg <- 2 close (msg) wg.Wait() }
对于一个无缓冲的channel,在启动一个消费者之前放数据就会报错
在放一个数据到channel中时,会阻塞数据,阻塞之前会放一把锁;只有当取值时,这把锁才会解开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package mainimport ( "fmt" "sync" ) var wg sync.WaitGroupvar lock sync.Mutexfunc consumer (data chan int ) { defer wg.Done() lock.Lock() for { msg, ok := <-data if !ok { break } fmt.Println(msg) } lock.Unlock() } func main () { var msg chan int msg = make (chan int ) go consumer(msg) msg <- 1 msg <- 3 close (msg) wg.Wait() }
双向和单向的channel 能使用单向channel就尽量使用单向channel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package mainimport ( "fmt" "sync" ) var wg sync.WaitGroupvar lock sync.Mutexfunc consumer (data <-chan int ) { defer wg.Done() lock.Lock() for { msg, ok := <-data if !ok { break } fmt.Println(msg) } lock.Unlock() } func producer (data chan <- int ) { defer wg.Done() for i := 0 ; i < 1000 ; i++ { data <- i } } func main () { var msg chan int msg = make (chan int ) go consumer(msg) go producer(msg) close (msg) wg.Wait() }
select select作用域通讯场景,伪随机选择一组可能的send和receive操作去执行。
考虑有多个case等待处理的情况,比如同时有多个channnel可以接收数据,那么select会伪随机选择一个case去处理;若无case需要处理,那么select会选择default取处理;如果没有default,那么select会阻塞,直到某个case需要处理。
select应用场景:
timeout超时机制
判断channel是否被阻塞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport ( "fmt" "time" ) func main () { var msg chan int msg = make (chan int , 1 ) go func () { time.Sleep(time.Second * 2 ) msg <- 1 }() select { case res := <-msg: fmt.Println(res) case <-time.After(time.Second): fmt.Println("timeout 1" ) } close (msg) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package mainimport ( "fmt" "time" ) func main () { msg1 := make (chan int , 1 ) msg2 := make (chan int , 1 ) go func () { time.Sleep(time.Second * 3 ) msg1 <- 1 }() go func () { time.Sleep(time.Second * 2 ) msg2 <- 1 }() select { case res := <-msg1: fmt.Println(res) case res := <-msg2: fmt.Println(res) default : fmt.Println("timeout" ) } close (msg1) close (msg2) }
场景:
假设在有一个goroutine,希望它在一定时间内监控cpu信息,之后退出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package mainimport ( "fmt" "sync" "time" ) var stop chan bool = make (chan bool )var wg sync.WaitGroupfunc cpuInfo () { defer wg.Done() for { select { case <-stop: fmt.Println("monitor closes" ) return default : time.Sleep(time.Second) fmt.Println("monitor is running" ) } } } func main () { wg.Add(1 ) go cpuInfo() time.Sleep(6 * time.Second) stop <- true wg.Wait() fmt.Println("goroutine finishes" ) }
context 考虑更复杂的场景,若有多个监控协程在运行,还想用上面的select加channel的方式来指定时间退出的操作,我们就需要向管道中传多个值。
这时引入context:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package mainimport ( "context" "fmt" "sync" "time" ) var wg sync.WaitGroupfunc cpuInfo (ctx context.Context) { defer wg.Done() for { select { case <-ctx.Done(): fmt.Println("cpu monitor finishes" ) return default : time.Sleep(time.Second) fmt.Println("cpu monitor is running" ) } } } func memoryInfo (ctx context.Context) { defer wg.Done() ctx2, _ := context.WithCancel(ctx) for { select { case <-ctx2.Done(): fmt.Println("memory monitor finishes" ) return default : time.Sleep(time.Second) fmt.Println("memory monitor is running" ) } } } func indicatorInfo (ctx context.Context) { defer wg.Done() for { select { case <-ctx.Done(): fmt.Println("indicator monitor finishes" ) return default : time.Sleep(time.Second) fmt.Println("indicator monitor is running" ) } } } func main () { ctx, cancel := context.WithCancel(context.Background()) wg.Add(3 ) go cpuInfo(ctx) go memoryInfo(ctx) go indicatorInfo(ctx) time.Sleep(6 * time.Second) cancel() wg.Wait() fmt.Println("all monitors finish" ) }
几种context:
1 2 3 context.WithCancel() context.WithDeadline() context.WithTimeout()
异常处理 随便说说
go异常处理思想: 若一个函数可能出现异常,那么就应把异常作为返回值,没有异常就返回nil
关于defer
panic和recover 错误和异常概念区分:
错误:错误你可以预判
异常:不可预判,往往是由于代码不严谨
错误:
错误的场景:
文件IO
数据库连接
空指针引用
下标越界
锁
…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport ( "errors" "fmt" ) func f (a, b int ) (int , error) { if b == 0 { return 0 , errors.New("hhhhh" ) } return a / b, nil } func main () { a := 1 b := 0 r, err := f(a, b) if err != nil { fmt.Println(err) } fmt.Println(r) }
抛出异常和捕捉异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport ( "fmt" ) func f (a, b int ) int { if b == 0 { panic ("hhhhh" ) } return a / b } func main () { a := 1 b := 0 defer func () { err := recover () if err != nil { fmt.Println(err) } }() fmt.Println(f(a, b)) }
在任一子协程中使用panic抛出错误会导致主协程挂掉,同时导致其它的协程挂掉
在一个协程中再开一个协程,父协程捕获不到子协程抛出的异常,所以会出现上述问题
一种处理方法:单独写一个函数用于捕获异常,每开一个协程就defer这个函数