zoukankan      html  css  js  c++  java
  • go-web摘抄1-基础知识

    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,值是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
    ", 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用于内建类型(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
    ", 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核数的最大值,并返回之前的值。

  • 相关阅读:
    【1801日語听解4】第14回:6月9日
    【日語听解2】第14回:6月8日
    【日語視聴説2】第14回:6月8日
    【日本語新聞編集】第13回:6月5日
    【1801日語写作】第13回:6月4日
    【日本語新聞選読】第13回:6月2日
    Win10版本
    家庭或小规模无线使用方
    批处理bat复制命令Copy与Xcopy
    批处理bat删除命令
  • 原文地址:https://www.cnblogs.com/callmelx/p/13929410.html
Copyright © 2011-2022 走看看