go 语言基础
1. hello,world
package main
import "fmt"
func main() {
fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい
")
}
2. go 基础
2.1 变量
定义变量:
var variableName type //声明一个变量,并未赋值
var vname1, vname2, vname3 type //声明多个变量
var variableName type = value //初始化“variableName”的变量为“value”值,类型是“type”
var vname1, vname2, vname3 type= v1, v2, v3
_, b := 34, 35 //_(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃
vname1, vname2, vname3 := v1, v2, v3 //:=这个符号直接取代了var和type,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部
var variable1 = v1 //省略type
var (
varable1 int,
varable2 string
//...
) //少写一些var
一般用var
方式来定义全局变量
2.2 常量
const constantName = value //常量可以是任何类型,所以可以不用写类型
//如果需要,也可以明确指定常量的类型:
const Pi float32 = 3.1415926
特殊常量iota
它默认开始值是0,const中每增加一行加1:
package main
import "fmt"
const (
a = iota
b
c = 123
d
)
func main() {
fmt.Println(a, b, c, d)
}
2.3 内置基础类型
2.3.1 boolean 布尔类型
var isActive bool// 全局变量声明
var enabled, disabled = true,false //忽略类型
func test(){
var avaiable bool
var := false
available = true
}
在Go中,布尔值的类型为bool
,值是true
或false
,默认为false
2.3.2 数值类型
整数:
整数类型有无符号和带符号两种。Go同时支持int
和uint
,这两种类型的长度相同,但具体长度取决于不同编译器的实现。Go里面也有直接定义好位数的类型:rune
, int8
, int16
, int32
, int64
和byte
, uint8
, uint16
, uint32
, uint64
。其中rune
是int32
的别称,byte
是uint8
的别称。
tip 注意: 这些类型的变量之间不允许互相赋值或操作,不然会在编译时引起编译器报错。 int8和uint8是两种不同的类型
浮点型
浮点数的类型有float32
和float64
两种(没有float
类型),默认是float64
。
复数:
Go还支持复数。它的默认类型是complex128
(64位实数+64位虚数)。如果需要小一些的,也有complex64
(32位实数+32位虚数)
字符串:
Go中的字符串都是采用UTF-8
字符集编码。字符串是用一对双引号(""
)或反引号(`
)括起来定义,它的类型是
string`
//示例代码
var frenchHello string // 声明变量为字符串的一般方法
var emptyString string = "" // 声明了一个字符串变量,初始化为空字符串
func test() {
no, yes, maybe := "no", "yes", "maybe" // 简短声明,同时声明多个变量
japaneseHello := "Konichiwa" // 同上
frenchHello = "Bonjour" // 常规赋值
}
字符串里面的值是不能被更改的,如果要修改,可以转成byte类型再转字符串
var s string = "hello"
s[0] = 'c //代码编译时会报错:cannot assign to s[0]
//一定要更改的话
s := "hello"
c := []byte(s) // 将字符串 s 转换为 []byte 类型
c[0] = 'c'
s2 := string(c) // 再转换回 string 类型
fmt.Printf("%s
", s2)
-
字符串连接:
+
-
多行字符串
m := `hello world`
数组:
数组定义方式:
var arr [n]type // n是长度 type是类型
对数组的操作和其它语言类似,都是通过[]
来进行读取或赋值
var arr [10]int // 声明了一个int类型的数组
arr[0] = 42 // 数组下标是从0开始的
arr[1] = 13 // 赋值操作
fmt.Printf("The first element is %d
", arr[0]) // 获取数据,返回42
fmt.Printf("The last element is %d
", arr[9]) //返回未赋值的最后一个元素,默认返回0
c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度
// 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有4个int类型的元素
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}
// 上面的声明可以简化,直接忽略内部的类型
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}
切片slice
slice 是数组的视图,他指向一个底层数组.
切片定义:
var fslice []int //和定义数组一直,只是少了长度
slice
可以从一个数组或一个已经存在的slice
中再次声明
// 声明一个含有10个元素元素类型为byte的数组
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// 声明两个含有byte的slice
var a, b []byte
// a指向数组的第3个元素开始,并到第五个元素结束,左闭又开
a = ar[2:5]
//现在a含有的元素: ar[2]、ar[3]和ar[4]
// b是数组ar的另一个slice
b = ar[3:5]
// b的元素是:ar[3]和ar[4]
slice的示例:
// 声明一个数组
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// 声明两个slice
var aSlice, bSlice []byte
// 演示一些简便操作
aSlice = array[:3] // 等价于aSlice = array[0:3] aSlice包含元素: a,b,c
aSlice = array[5:] // 等价于aSlice = array[5:10] aSlice包含元素: f,g,h,i,j
aSlice = array[:] // 等价于aSlice = array[0:10] 这样aSlice包含了全部的元素
// 从slice中获取slice
aSlice = array[3:7] // aSlice包含元素: d,e,f,g,len=4,cap=7
bSlice = aSlice[1:6] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f
fmt.Println(aSlice)
fmt.Println(bSlice)
bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f
bSlice = aSlice[0:5] // 对slice的slice可以在cap范围内扩展,此时bSlice包含:d,e,f,g,h
bSlice = aSlice[:] // bSlice包含所有aSlice的元素: d,e,f,g
map字段:
map[keyType]valueType
使用实例
// 初始化一个字典
rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// map有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true
csharpRating, ok := rating["C#"]
if ok {
fmt.Println("C# is in the map and its rating is ", csharpRating)
} else {
fmt.Println("We have no rating associated with C# in the map")
}
delete(rating, "C") // 删除key为C的元素
错误类型
go内置一个error类型
err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
fmt.Print(err)
}
new 与make操作:
make
用于内建类型(map
、slice
和channel
)的内存分配。new
用于各种类型的内存分配。
内建函数make(T, args)
与new(T)
有着不同的功能,make只能创建slice
、map
和channel
,并且返回一个有初始值(非零)的T
类型,而不是*T
。本质来讲
3. 流程和函数
3.1 条件判断
- if判断
Go的if
还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了
if x:=computedValue();x>10{
//...
fmt.Println("x is greater than 10");
}else{
fmt.Println("x is less than 10");
}
-
switch
switch sExpr{ case expr1: some instructions case expr2: some instructions case expr3: some instructions default: other code }
sExpr
和expr1
,expr2
,expr3
的类型必须一致.go 默认每个case后面都带有一个 break,如果强制执行后面的的,可以使用fallthrough
,注意:如果sExpr
是一个表达式的话后面的case类型就是bool类型了,比如switch a>1{...}
3.2 循环
-
for循环:
语法
for expression1; expression2; expression3 { //... }
expression1
、expression2
和expression3
都是表达式,其中expression1
和expression3
是变量声明或者函数调用返回值之类的,expression2
是用来条件判断,expression1
在循环开始之前调用,expression3
在每轮循环结束之时调用。示例:
for x, y := 1, 2; x <= 10; x++ { y++ fmt.Println(x) } //... //省略所有循环条件,形成while(true判断) for { //... } // func main() { x := 1 for x < 10 { x++ fmt.Println(x) } }
3.3 无条件跳转
-
go
用
goto
跳转到必须在当前函数内定义的标签func myfunc(){ i:=0 Here: // fmt.Println(i) i++ goto Here }
3.4函数
函数是Go里面的核心设计,它通过关键字func
来声明,它的格式如下:
func funName(input1 type1,input2 type2)(output1 type1,output2 type2){
//...
return value1,value2
}
最好命名返回值
func SumAndProduct(A, B int) (add int, Multiplied int) {
add = A+B
Multiplied = A*B
return
}
- 变参:
go函数支持变参,接受不定数量的参数,类型需要一致
func funcName(arg... int){}
-
传值与传指针:
通过函数改变参数的值时,函数外的参数不会受到影响,应为传入的是外层变量的一个copy
package main import ( "fmt" ) func main() { param := 1 add1(¶m) fmt.Println("this is param ", param) } func add1(param *int) int { *param++ fmt.Println("here add1 ", *param) return *param }
-
指针的好处:
传指针可以使多个函数操作同一个对象
大的结构体时,用指针比较好
3.5 defer
defer
语义为延时, 当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回
func main() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
defer fmt.Println("start...")
}
/**
打印结果如下:
start...
3
2
1
*/
3.6 函数作为值,类型
在Go中函数也是一种变量,我们可以通过type
来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
示例说明
package main
import "fmt"
type testInt func(int) bool // 声明了一个函数类型
func isOdd(integer int) bool {
if integer%2 == 0 {
return false
}
return true
}
func isEven(integer int) bool {
if integer%2 == 0 {
return true
}
return false
}
// 声明的函数类型在这个地方当做了一个参数
func filter(slice []int, f testInt) []int {
var result []int
for _, value := range slice {
if f(value) {
result = append(result, value)
}
}
return result
}
func main(){
slice := []int {1, 2, 3, 4, 5, 7}
fmt.Println("slice = ", slice)
odd := filter(slice, isOdd) // 函数当做值来传递了
fmt.Println("Odd elements of slice are: ", odd)
even := filter(slice, isEven) // 函数当做值来传递了
fmt.Println("Even elements of slice are: ", even)
}
函数作为类型,就是定义一个类型,作为参数/变量,在赋值时,变为具体的函数来使用.类似 一层抽象的过程.
3.7 panic 和recover
go没有类似java的抛出异常的机制,它不能抛出异常,而是使用pannic和recover的方式. 一定要记住,你应当把它作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少有panic
的东西
panic
:
3.8 main函数和init函数
main
和init
函数是go的两个保留函数,init函数可以作用在任何包,main只能作用域package main的包里.
init
类似其他语言中的构造方法,在初始化变量和常量后,会调用init
3.9 import
import常用的几种方式:
-
点操作:
这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名, 也就是前面你调用的fmt.Println("hello world")可以省略的写成Println("hello world")
import( . "fmt" )
-
别名操作
可以把包命名成另一个我们用起来容易记忆的名字
import( f "fmt" )
-
_操作
_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。
import ( "database/sql" _ "github.com/ziutek/mymysql/godrv" )
4. struct结构体
struct 类似c 语言中的结构体,可以通过他来实现一些对象功能
定义struct结构体如下:
type person struct {
name string
age int
}
使用结构体:
var p person
p.name = "zhagsna"
p.age = 21
fmt.Println(p.name)
//另一种使用
p:=person{"tom",21}
p:=person{age:21}
P:= new(person) //通过new 函数分配一个指针,此处类型为*p
匿名字段:
type Human struct {
name string
age int
weight int
}
type Student struct {
Human // 匿名字段,那么默认Student就包含了Human的所有字段
speciality string
}
5. 面向对象
5.1 method
struct中的类似其他语言中的方法定义方式如下:
func (r ReceiverType) funcName(parameters) (results)
如果在方法名前有一个接受者
相当于给某个类型增加了一个方法,不仅仅限于struct
type money float32
func (m money) add() {
fmt.Println("Now im in money")
}
func main() {
var money money = 12
money.add()
}
别名money 增加方法add
5.2 指针作为reciver
5.3 method方法继承
如果匿名字段实现了一个method,那么包含这个匿名字段的struct也能调用该method
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名字段
school string
}
type Employee struct {
Human //匿名字段
company string
}
//在human上面定义了一个method
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s
", h.name, h.phone)
}
func main() {
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
mark.SayHi()
sam.SayHi()
}
5.4 method方法重写
method方法的重写,上面的SayHi
的接收者更改一下就可以了,或者再写一个新的
6. interface 接口
6.1 什么是interface
interface是一组method组合,我们通过interface定义接口的一组行为
6.2 interface 类型
type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名字段Human
school string
loan float32
}
type Employee struct {
Human //匿名字段Human
company string
money float32
}
//Human对象实现Sayhi方法
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s
", h.name, h.phone)
}
// Human对象实现Sing方法
func (h *Human) Sing(lyrics string) {
fmt.Println("La la, la la la, la la la la la...", lyrics)
}
//Human对象实现Guzzle方法
func (h *Human) Guzzle(beerStein string) {
fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}
// Employee重载Human的Sayhi方法
func (e *Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s
", e.name,
e.company, e.phone) //此句可以分成多行
}
//Student实现BorrowMoney方法
func (s *Student) BorrowMoney(amount float32) {
s.loan += amount // (again and again and...)
}
//Employee实现SpendSalary方法
func (e *Employee) SpendSalary(amount float32) {
e.money -= amount // More vodka please!!! Get me through the day!
}
// 定义interface
type Men interface {
SayHi()
Sing(lyrics string)
Guzzle(beerStein string)
}
type YoungChap interface {
SayHi()
Sing(song string)
BorrowMoney(amount float32)
}
type ElderlyGent interface {
SayHi()
Sing(song string)
SpendSalary(amount float32)
}
interface可以被任意对象实现,任意对象都实现了空interface
6.3 interface值
interface是一组抽象方法的集合,里面没有变量,他必须由其他非interface类型实现,
示例:
type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名字段
school string
loan float32
}
//Human实现SayHi方法
func (h Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s
", h.name, h.phone)
}
//Human实现Sing方法
func (h Human) Sing(lyrics string) {
fmt.Println("La la la la...", lyrics)
}
//men接口
type Men interface {
SayHi()
Sing(lyrics string)
}
//...
//接口类型的变量,他的值可以是任意实现了该接口的对象类型
var i Men== Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
i.SayHi()
6.4 interface参数
interface的变量可以持有任意实现interface类型的对象.
实现接受所有参数的方法,例如fmt.Println()
fmt库实现了Stringer接口
type Stringer interface {
String() string
}
go的fmt部分源码如下:
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintln(a)
n, err = w.Write(p.buf)
p.free()
return
}
// Println 使用默认格式对 a 中提供的 arg 进行格式化,并将结果写入标准输出。
// 返回写入的字节数和错误信息。
// 各 arg 之间会添加空格,并在最后添加换行符。
func Println(a ...interface{}) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
6.5 变量存储的类型
判断一个变量是不是属于某一个实现类型,类似于php中的instanceof
目前有两种方式如下:
-
Comma-ok:
Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型
type Element interface{} //定义这个类型是为了接受任意类型的变量 type List [] Element ... //定义多类型的的数组对象 list := make(List, 3) list[0] = 1 // an int list[1] = "Hello" // a string list[2] = Person{"Dennis", 70} for index, element := range list { if value, ok := element.(int); ok { fmt.Printf("list[%d] is an int and its value is %d ", index, value) } else if value, ok := element.(string); ok { fmt.Printf("list[%d] is a string and its value is %s ", index, value) } else if value, ok := element.(Person); ok { fmt.Printf("list[%d] is a Person and its value is %s ", index, value) } else { fmt.Printf("list[%d] is of a different type ", index) } }
-
switch 测试
只是换了一种方式,晒his利用elelmet.(type)的形式
for index, element := range list{ switch value := element.(type) { case int: fmt.Printf("list[%d] is an int and its value is %d ", index, value) case string: fmt.Printf("list[%d] is a string and its value is %s ", index, value) case Person: fmt.Printf("list[%d] is a Person and its value is %s ", index, value) default: fmt.Println("list[%d] is of a different type", index) } }
6.6 嵌入interface
一个interface1作为interface2的一个嵌入字段
//io包下面的 io.ReadWriter ,它包含了io包下面的Reader和Writer两个interface:
// io.ReadWriter
type ReadWriter interface {
Reader
Writer
}
6.7 反射reflect
所谓反射就是检测程序运行时的状态.
使用reflect包的三个步骤
- 要去反射是一个类型的值(这些值都实现了空interface),首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。这两种获取方式如下
t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
使用实例:
import (
"fmt"
"reflect"
)
type Person struct {
name string
age int
}
func main() {
var x Person
x.name = "张三"
x.age = 12
v := reflect.TypeOf(x) //转化成reflect类型
v1 := reflect.ValueOf(x)
fmt.Println("type:", v)
fmt.Println(v1)
}
- 转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值,例如
tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签
name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值
- 最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理。反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)
7. 并发
7.1 goroutine
goroutine说到底其实就是线程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。
goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过go
关键字实现了,其实就是一个普通的函数。
gorountine语法如下:
go hello(a,b,c)
使用示例:
package main
import (
"fmt"
"runtime"
)
type Person struct {
name string
age int
}
func say(s string) {
for i := 0; i < 5; i++ {
runtime.Gosched() //runtime.Gosched()表示让CPU把时间片让给别人,下次某个时候继续恢复执行该goroutine
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
7.2 channels
goroutine运行在相同的地址空间,因此共享内存必须做好同步.goroutine之间通过channel进行数据通信.
goroutine 通过channel进行发送和接收数据,但这些数值必须是channel类型,需要注意的是创建channel时,必须使用make创建channel
channel通过操作符<-
来接收和发送数据
ch <- v // 发送v到channel ch.
v := <-ch // 从ch中接收数据,并赋值给v
使用实例:
启动一个goroutine,启动goroutine 其实是一个异步的步骤,所以使用return 是存在问题的,如果遇到耗时的比如io操作,会长时间得不到响应.所以需要通过channel来进行特殊处理,进行数据的传输
package main
import "fmt"
func sum(a []int, c chan int) {
total := 0
for _, v := range a {
total += v
}
c <- total // send total to c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0} //定义数组
c := make(chan int) //创建channel, chan这个是个关键字啊
go sum(a[:len(a)/2], c) //启动一个goroutine
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock。 所谓阻塞,也就是如果读取(value := <-ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<-5)将会被阻塞,直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。
7.3 Buffered channels
上面我们介绍了默认的非缓存类型的channel,不过Go也允许指定channel的缓冲大小,很简单,就是channel可以存储多少元素。ch:= make(chan bool, 4),创建了可以存储4个元素的bool 型channel。在这个channel 中,前4个元素可以无阻塞的写入。当写入第5个元素时,代码将会阻塞,直到其他goroutine从channel 中读取一些元素,腾出空间。
ch := make(chan type, value)
value == 0 ! 无缓冲(阻塞)
value > 0 ! 缓冲(非阻塞,直到value 个元素)
实例:
package main
import "fmt"
func main() {
c := make(chan int, 2)//修改2为1就报错,修改2为3可以正常运行
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
//修改为1报如下的错误:
//fatal error: all goroutines are asleep - deadlock!
说明一下为什么会报错:
-
因为channel是沟通goroutine的桥梁,默认的情况,channel接收和发送数据都是阻塞的.
-
因为channel是一个全双工的的收发,但是进行一次信息的交互,需要两端同时做好准备才能进行通讯,
-
阻塞时必须两端都准备好连接关系,比如一端监听某个端口,
-
无缓存式的channel会报错,因为发送和接收之间的关系没有建立,如果是缓存式的相当于将发送过这缓存起来,做一个类似连接池的东西,在使用时对里面的发送端进行配对,等接收端数量大于发送端数量时,又会产生阻塞的情况.
7.4 Range和Close
上面这个例子中,我们需要读取两次c,这样不是很方便,Go考虑到了这一点,所以也可以通过range,像操作slice或者map一样操作缓存类型的channel
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 1, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x + y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
for i := range c
能够不断的读取channel里面的数据,直到该channel被显式的关闭。上面代码我们看到可以显式的关闭channel,生产者通过内置函数close
关闭channel。关闭channel之后就无法再发送任何数据了,在消费方可以通过语法v, ok := <-ch
测试channel是否被关闭。如果ok返回false,那么说明channel已经没有任何数据并且已经被关闭。
7.5 select
对于多个channel的情况,可以使用关键字select
,通过select监听channel上面的数据流动
select 默认也是阻塞的,只有当监听的channel中可以正常发送或接收时,select才会运行,当多个channel都准备好是,select会随机选择一个执行
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 1, 1
for {
select {
case c <- x:
x, y = y, x + y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
7.6 超时
有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用select来设置超时,通过如下的方式实现:
func main() {
c := make(chan int)
o := make(chan bool)
go func() {
for {
select {
case v := <- c:
println(v)
case <- time.After(5 * time.Second):
println("timeout")
o <- true
break
}
}
}()
<- o
}
7.6 runtime goroutine
runtime包中有几个处理goroutine的函数:
-
Goexit
退出当前执行的goroutine,但是defer函数还会继续调用
-
Gosched
让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。
-
NumCPU
返回 CPU 核数量
-
NumGoroutine
返回正在执行和排队的任务总数
-
GOMAXPROCS
用来设置可以并行计算的CPU核数的最大值,并返回之前的值。