go-web摘抄1-基础知识

go 语言基础

1. hello,world

package main

import "fmt"

func main() {
    fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい\n")
}

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,值是truefalse,默认为false

2.3.2 数值类型

整数:

整数类型有无符号和带符号两种。Go同时支持intuint,这两种类型的长度相同,但具体长度取决于不同编译器的实现。Go里面也有直接定义好位数的类型:rune, int8, int16, int32, int64byte, uint8, uint16, uint32, uint64。其中runeint32的别称,byteuint8的别称。

tip 注意: 这些类型的变量之间不允许互相赋值或操作,不然会在编译时引起编译器报错。 int8和uint8是两种不同的类型

浮点型

浮点数的类型有float32float64两种(没有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\n", 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\n", arr[0])  // 获取数据,返回42
fmt.Printf("The last element is %d\n", 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用于内建类型(mapslicechannel)的内存分配。new用于各种类型的内存分配。

内建函数make(T, args)new(T)有着不同的功能,make只能创建slicemapchannel,并且返回一个有初始值(非零)的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
    }
    

    sExprexpr1,expr2,expr3的类型必须一致.go 默认每个case后面都带有一个 break,如果强制执行后面的的,可以使用fallthrough,注意:如果sExpr是一个表达式的话后面的case类型就是bool类型了,比如switch a>1{...}

3.2 循环

  • for循环:

    语法

    for expression1; expression2; expression3 {
        //...
    }
    

    expression1expression2expression3都是表达式,其中expression1expression3是变量声明或者函数调用返回值之类的,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(&param)
    	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函数

maininit函数是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\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()
}

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\n", 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\n", 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\n", 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\n", index, value)
        } else if value, ok := element.(string); ok {
            fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
        } else if value, ok := element.(Person); ok {
            fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
        } else {
            fmt.Printf("list[%d] is of a different type\n", 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\n", index, value)
             case string:
             fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
             case Person:
             fmt.Printf("list[%d] is a Person and its value is %s\n", 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核数的最大值,并返回之前的值。

posted @ 2020-11-04 23:32  callmelx  阅读(117)  评论(0编辑  收藏  举报