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核数的最大值,并返回之前的值。

  • 相关阅读:
    公用表表达式(CTE)的递归调用
    c# 如何让tooltip显示文字换行
    实战 SQL Server 2008 数据库误删除数据的恢复
    SQL SERVER数据库中 是否可以对视图进行修改删除
    asp.net中实现文件批量上传
    sql server 2008学习2 文件和文件组
    sql server 2008学习3 表组织和索引组织
    sql server 2008学习4 设计索引的建议
    sql server 2008学习10 存储过程
    .net 调用 sql server 自定义函数,并输出返回值
  • 原文地址:https://www.cnblogs.com/callmelx/p/13929410.html
Copyright © 2011-2022 走看看