学习资料来自:
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/preface.md
目录:
package main //声明为独立运行程序的包
import "fmt" //引用模块,类似头文件
//函数入口,需要func,语句结尾也不需要;
func main() {
fmt.Printf("Hello, world or 你好,世界\n")
}
//Go
var variableName type
//C++
type variableName;
//Go
var varName1,varName2,varName3 type
//C++
type varName1, varName2, varName3;
//Go
var variableName type = value
var varName1,varName2,varName3 type = val1, val2, val3
//C++
type variableName = value;
type varName1 = val1, varName2 = val2, varName3 = val3;
:=这个符号直接取代了var和type,但是只能用在函数内部,一般用var方式来定义全局变量。
_(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。
var c = 20 //自行判断类型
a := 30
_, b := 34, 35
//Go 类型可写可不写
const a int = 5
const Pi = 3.1415926
const str = "string"
//C++
#define a 5 //直接替换
const double Pi = 3.1415926; //类型控制
const char[10] str = "string";
这些类型的变量之间不允许互相赋值或操作!
bool //布尔型
int //整型 32bit
uint //无符号整型 32bit
int8 //1bit
uint8 == byte //字节 1bit
int16 //短整型 16bit
uint16
int32 == rune //和int一样32bit,但不能互用
uint32
int64 //长整型 64bit
uint64
//没有float类型,默认是float64
float32 //浮点型
float64 //类似C++的double
complex64 //短复数,(32位实数+32位虚数)
complex128 //默认长复数,(64位实数+64位虚数)
默认值:
int 0
int8 0
int32 0
int64 0
uint 0x0
rune 0 //rune的实际类型是 int32
byte 0x0 // byte的实际类型是 uint8
float32 0 //长度为 4 byte
float64 0 //长度为 8 byte
bool false
string ""
type 类似C的 typedef
type ages int
type money float32
type months map[string]int
m := months {
"January":31,
"February":28,
...
"December":31,
}
Go中的字符串都是采用UTF-8字符集编码。
var zh string //声明字符串
var hi string = "Hello" //初始化字符串
no, yes, maybe := "no", "yes", "maybe"
//多行字符串
m := `hello
world`
//可以使用 + 来连接字符串
h := "hello"
w := "world"
hw := h + w //"hello world"
修改字符串,string字符串保存在静态数据区,数组在栈区
//yes[0] = "X" //X,不能修改字符串常量
temp := []byte(yes) //将yes转为[]byte类型
temp[0] = 'x' //√,数组可以修改
xes := string(temp) //转回string
//字符串虽不能更改,但可进行切片操作
c := "cello"
h := "h" + c[1:] //"hello"
Go内置有一个error类型,专门用来处理错误信息,Go的package里面还专门有一个包errors来处理错误:
err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
fmt.Print(err)
}
同时声明多个常量、变量,或者导入多个包时,可采用分组的方式进行声明。
import(
"fmt"
"os"
)
const(
i = 100
pi = 3.1415
prefix = "Go_"
)
var(
i int
pi float32
prefix string
)
这个关键字用来声明enum的时候采用,它默认开始值是0,const中每增加一行加1:
package main
import (
"fmt"
)
const (
x = iota // x == 0
y = iota // y == 1
z = iota // z == 2
w // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面y和z可同样不用"= iota"
)
const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0
const (
h, i, j = iota, iota, iota //h=0,i=0,j=0 iota在同一行值相同
)
const (
a = iota //a=0
b = "B"
c = iota //c=2
d, e, f = iota, iota, iota //d=3,e=3,f=3
g = iota //g = 4
)
func main() {
fmt.Println(a, b, c, d, e, f, g, h, i, j, x, y, z, w, v)
}
Go之所以会那么简洁,是因为它有一些默认的行为:
[3]int与[4]int是不同的类型,数组也就不能改变长度。
数组之间是值传递,传入函数为副本;
需要使用指针使用slice。
var arr [10]int // 声明了一个int类型的数组
arr[0] = 42 // 数组下标是从0开始的
arr[1] = 13 // 赋值操作
a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组
b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为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和数组在声明时的区别:声明数组时,[num]or[…],而声明slice时,[]。
slice并不是真正意义上的动态数组,而是一个引用类型。
// 和声明array一样,只是少了长度
var fslice []int
// 声明一个含有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有一些简便的操作:
slice有几个有用的内置函数:
注:append函数会改变slice所引用的数组的内容,从而影响到引用同一数组的其它slice。 但当slice中没有剩余空间(即(cap-len) == 0)时,此时将动态分配新的数组空间。返回的slice数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的slice则不受影响。
从Go1.2开始slice支持了三个参数(容量)的slice
var array [10]int
slice := array[2:4:7] //容量最大7,无法访问array后3个元素
map也就是Python中字典(哈希表)的概念,它的格式为
map [keyType] valueType
map和slice类似,只是slice的index只能是int类型,map可以是很多类型。
// 声明一个key是字符串,值为int的字典,这种方式的声明需要在使用之前使用make初始化
var numbers1 map[string]int
numbers1 = make(map[string]int)
// 另一种map的声明方式
numbers := make(map[string]int)
numbers["one"] = 1 //赋值
numbers["ten"] = 10 //赋值
numbers["three"] = 3
fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据
// 打印出来如:第三个数字是: 3
使用map过程中需要注意的几点:
通过delete删除map的元素:
// 初始化一个字典
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的元素
map也是一种引用类型,如果两个map同时指向一个底层,那么一个改变,另一个也相应的改变:
m := make(map[string]string)
m["Hello"] = "Bonjour"
m1 := m
m1["Hello"] = "Salut" // 现在m["hello"]的值已经是Salut了
make用于内建类型(map、slice 和channel)的内存分配。
new用于各种类型的内存分配。
new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值。(类似C++的new操作符)
make只能创建slice、map和channel,并且返回一个有初始值(非零)的T类型,而不是*T。
Go里面if条件判断语句中不需要括号
if x > 10 {
fmt.Println("x is greater than 10")
} else {
fmt.Println("x is less than 10")
}
Go的if还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示
// 计算获取值x,然后根据x返回的大小,判断是否大于10。
if x := computedValue(); x > 10 {
fmt.Println("x is greater than 10")
} else {
fmt.Println("x is less than 10")
}
//这个地方如果这样调用就编译出错了,因为x是条件里面的变量
fmt.Println(x)
标签名是大小写敏感的。
func myFunc() {
i := 0
Here: //这行的第一个词,以冒号结束作为标签
println(i)
i++
goto Here //跳转到Here去
}
Go是没有while循环的,所以它又可以当作while来使用。
语法:
for expression1; expression2; expression3 {
//...
}
例子:
package main
import "fmt"
func main(){
sum := 0;
for index:=0; index < 10 ; index++ {
sum += index
}
fmt.Println("sum is equal to ", sum)
}
// 输出:sum is equal to 45
有些时候如果我们忽略expression1和expression3:
sum := 1
for ; sum < 1000; {
sum += sum
}
也可以忽略; 就变成while了:
sum := 1
for sum < 1000 {
sum += sum
}
在循环里面有两个关键操作break和continue:
for配合range可以用于读取slice和map的数据:
for k,v:=range map {
fmt.Println("map's key:",k)
fmt.Println("map's val:",v)
}
由于 Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用_来丢弃不需要的返回值 例如:
for _, v := range map{
fmt.Println("map's val:", v)
}
Go里面switch默认相当于每个case最后带有break,所以不需要我们写,也可以使用fallthrough强制执行后面的case代码。
语法:
switch sExpr {
case expr1:
some instructions
case expr2:
some other instructions
case expr3:
some other instructions
default:
other code
}
例子:
i := 10
switch i {
case 1:
fmt.Println("i is equal to 1")
case 2, 3, 4:
fmt.Println("i is equal to 2, 3 or 4")
case 10:
fmt.Println("i is equal to 10")
default:
fmt.Println("All I know is that i is an integer")
}
它通过关键字func来声明,它的格式如下:
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
//这里是处理逻辑代码
//返回多个值
return value1, value2
}
上面的代码我们看出:
Go函数支持变参。接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参:
func myfunc(arg ...int) {}
arg …int告诉Go这个函数接受不定数量的参数。注意,这些参数的类型全部是int。在函数体中,变量arg是一个int的slice:
for _, n := range arg {
fmt.Printf("And the number is: %d\n", n)
}
当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。
可以使用 & 来取地址
Go语言中channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)
函数结束时调用defer的语句,类似类的析构函数,可以在函数中添加多个defer语句,如果有多个defer,则按照后进先出(栈)依次执行 。
func ReadWrite() bool {
file.Open("file")
defer file.Close()
if failureX {
return false
}
if failureY {
return false
}
return true
}
在Go中函数也是一种变量,我们可以通过type来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型。
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)
}
Go没有像Java那样的异常机制,它不能抛出异常,而是使用了panic和recover机制。一定要记住,你应当把它作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少有panic的东西。这是个强大的工具,请明智地使用它。
Panic
是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。当函数F调用panic,函数F的执行被中断,但是F中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。恐慌可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组。
Recover
是一个内建的函数,可以让进入令人恐慌的流程中的goroutine恢复过来。recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。如果当前的goroutine陷入恐慌,调用recover可以捕获到panic的输入值,并且恢复正常的执行。
下面这个函数演示了如何在过程中使用panic
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
面这个函数检查作为其参数的函数在执行时是否会产生panic:
unc throwsPanic(f func()) (b bool) {
defer func() {
if x := recover(); x != nil {
b = true
}
}()
f() //执行函数f,如果f中出现了panic,那么就可以恢复回来
return
}
导入包文件
1.相对路径
import “./model” //当前文件同一目录的model目录,但是不建议这种方式来import
2.绝对路径
import “shorturl/model” //加载gopath/src/shorturl/model模块
import(
"fmt"
)
fmt.Println("hello world")
import常用的几种方式:
1.点操作 (省略前缀的包名)
import(
. "fmt"
)
Println("hello world")
2.别名操作
import(
f "fmt"
)
f.Println("hello world")
3._操作
_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。
import (
"database/sql"
_ "github.com/ziutek/mymysql/godrv"
)
和C或者其他语言一样,我们可以声明新的类型
type person struct {
name string
age int
}
var P person // P现在就是person类型的变量了
//1.一般声明
P.name = "Astaxie" // 赋值"Astaxie"给P的name属性.
P.age = 25 // 赋值"25"给变量P的age属性
//2.按照顺序提供初始化值
P := person{"Tom", 25}
//3.通过field:value的方式初始化,这样可以任意顺序
P := person{age:24, name:"Tom"}
//4.当然也可以通过new函数分配一个指针,此处P的类型为*person
P := new(person)
fmt.Printf("The person's name is %s", P.name) // 访问P的name属性.
struct里面可以定义所有类型,包括内置
package main
import "fmt"
type Human struct {
name string
age int
weight int
}
type Student struct {
Human // 匿名字段,那么默认Student就包含了Human的所有字段
speciality string
}
func main() {
// 我们初始化一个学生
mark := Student{Human{"Mark", 25, 120}, "Computer Science"}
// 我们访问相应的字段
fmt.Println("His name is ", mark.name)
fmt.Println("His age is ", mark.age)
fmt.Println("His weight is ", mark.weight)
fmt.Println("His speciality is ", mark.speciality)
// 修改对应的备注信息
mark.speciality = "AI"
fmt.Println("Mark changed his speciality")
fmt.Println("His speciality is ", mark.speciality)
// 修改他的年龄信息
fmt.Println("Mark become old")
mark.age = 46
fmt.Println("His age is", mark.age)
// 修改他的体重信息
fmt.Println("Mark is not an athlet anymore")
mark.weight += 60
fmt.Println("His weight is", mark.weight)
}
给函数指定作用域,变成外界不可访问的该作用域的函数
类似C++类函数,只能当前类使用
package main
import (
"fmt"
"math"
)
type Rectangle struct {
width, height float64
}
type Circle struct {
radius float64
}
func (r Rectangle) area() float64 {
return r.width*r.height
}
func (c Circle) area() float64 {
return c.radius * c.radius * math.Pi
}
func main() {
r1 := Rectangle{12, 2}
r2 := Rectangle{9, 4}
c1 := Circle{10}
c2 := Circle{25}
fmt.Println("Area of r1 is: ", r1.area())
fmt.Println("Area of r2 is: ", r2.area())
fmt.Println("Area of c1 is: ", c1.area())
fmt.Println("Area of c2 is: ", c2.area())
}
func main() {
r1 := Rectangle{12, 2}
r2 := Rectangle{9, 4}
fmt.Println("Area of r1 is: ", area(r1))
fmt.Println("Area of r2 is: ", area(r2))
}
method的两个技巧:
就是C++类有个成员变量(类),这个类可以直接调用成员变量的函数
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\n", 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()
}
就是C++类函数和成员变量(类)的函数重名,优先调用自己的函数。
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\n", h.name, h.phone)
}
//Employee的method重写Human的method
func (e *Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //Yes you can split into 2 lines here.
}
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()
}
interface是一组method的集合,它把所有的具有共性的方法定义在一起。
package main
import (
"fmt"
)
//电话基本功能打电话
type Phone interface {
call()
}
type NokiaPhone struct {
}
//诺基亚能打电话
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
//苹果能打电话
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
phone.call()
phone = new(IPhone)
phone.call()
}
interface变量能保存具有interface方法的变量,如上面的例子:
phone = NokiaPhone
phone = IPhone
空interface可以存储任意类型,如果一个函数返回interface{},那么也就可以返回任意类型的值,它有点类似于C语言的void*类型。
// 定义a为空接口
var a interface{}
var i int = 5
s := "Hello world"
// a可以存储任意类型的数值
a = i
a = s
interface的变量可以持有任意实现该interface类型的对象。
举个例子:fmt.Println是我们常用的一个函数,但是你是否注意到它可以接受任意类型的数据。打开fmt的源码文件,你会看到这样一个定义:
type Stringer interface {
String() string
}
//fmt.Println实现类似如下
func (f fmt) Println(s string, str Stringer ) string {
...
}
也就是说,任何实现了String方法的类型都能作为参数被fmt.Println调用,让我们来试一试
package main
import (
"fmt"
"strconv"
)
type Human struct {
name string
age int
phone string
}
// 通过这个方法 Human 实现了 fmt.Stringer
func (h Human) String() string {
return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years - ✆ " +h.phone+"❱"
}
func main() {
Bob := Human{"Bob", 39, "000-7777-XXX"}
fmt.Println("This Human is : ", Bob)
}
目前常用的有两种方法:
1.Comma-ok断言
value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,有则true,element是interface变量,T是断言的类型。
2.switch测试
switch使用不需要获取value和ok
switch value := element.(type) {
case int:
case string:
case Person:
default:
interface可以嵌入interface
反射就是能检查程序在运行时的状态,反射的字段必须是可修改的。
一般用到的包是reflect包,使用reflect一般分成三步:
1). 首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。
t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
2).将reflect对象转化成相应的值
tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签
name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值
3).获取反射值能返回相应的类型和数值
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。
goroutine通过go关键字实现:
package main
import (
"fmt"
"runtime"
)
func say(s string) {
for i := 0; i < 5; i++ {
//协程是在同一线程内,所以需要让给别人运行一段时间再切回来
runtime.Gosched()
fmt.Println(s)
}
}
func main() {
go say("world") //开一个新的Goroutines执行
say("hello") //当前Goroutines执行
}
Go提供了一个很好的通信机制channel,接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock,如果读取的channel没有数据会阻塞直到数据到来。channel可以与Unix shell 中的双向管道做类比。
channels 通过chan关键字实现,并且必须使用make创建:
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)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x + y)
}
Buffered Channels就是channel可以存储多个元素。
ch := make(chan type, value)
当 value = 0 时,channel 是无缓冲阻塞读写的,当value > 0 时,channel 有缓冲、是非阻塞的,直到写满 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,而不是消费的地方去关闭它,这样容易引起panic
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)
}
}
另外记住一点的就是channel不像文件之类的,不需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的
通过select可以监听多个channel上的数据流动。
select默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个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)
}
在select里面还有default语法,select其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。
select {
case i := <-c:
// use i
default:
// 当c阻塞的时候执行这里
}
利用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 //超时退出程序
}
Goexit
退出当前执行的goroutine,但是defer函数还会继续调用
Gosched
让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。
NumCPU
返回 CPU 核数量
NumGoroutine
返回正在执行和排队的任务总数
GOMAXPROCS
用来设置可以并行计算的CPU核数的最大值,并返回之前的值。
二十五个关键字,记熟基础基本差不多了。
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var