《Go语言编程》读书笔记

2016-09-14
读书笔记

安装

  • 下载
  • 配置工作空间
    • export GOPATH=~/Documents/code/workspace_go
    • export PATH=$PATH:$GOPATH/bin
  • 工作空间workspace_go
    • mkdir -p $GOPATH/src/github.com/mamian

初识Go语言

语言特性

自动垃圾回收
更丰富的内置类型
  • map
  • slice:可动态增长的数组
函数多返回值
错误处理
  • defer:不管程序是否出现异常,均 在函数退出时自动执行相关代码
  • panic
  • recover
匿名函数和闭包
f := func(x, y int) int {
    return x + y
}
类型struct和接口interface
  • 不支持继承和重载,而只是支持了最基本的类型组合功能。
并发编程
  • 使用goroutine而不是用操作系统的并发机制。
  • 使用消息传递来共享内存而不是使用共享内存来通信。
  • Go语言通过系统的线程来多派这些函数的执行,使得每个用go关键字执行的函数可以行成为一个单位协程。当一个协程阻塞的时候,调度器就会自动把其他协程排到另外的线程中去执行,从而实现了程序无等并行化行。
  • 跨goroutine通信:channel
  • 由于一个进程内创建的所有goroutine行在同一个内存地间中,因此如果不同的goroutine不得不去访问共享的内存变量,访问前应该先获取相应的读写。Go语言标准库中的sync包提供了完的读写功能。
反射
语言交互性

第一个Go程序

  • 必须package main,但文件名不一定为main.go,必包含func main(){}方法。命令行传入的参数在os.Args变量中保存。如果需要支持命令行开关,可使用flag包。
  • go函数

    func 函数名(参数列表)(返回值列表) {
        //函数体
    }
    
    • 函数返回时没有被明确赋值的返回值都会被设置为认值。
  • 编译程序
    • 直接运行:go run hello.go
    • 编译后运行
      • go build hello.go
      • ./hello
  • 日志
    • fmt.Printf() fmt.Println()
    • log
  • GDB调试
    • build出可执行文件后,直接gdb helloworld

顺序编程

变量

变量声明
  • var

    var v1 int
    var v2 string
    var v3 [10]int          //数组
    var v4 []int            //数组切片
    var v5 struct {
        f int
    }
    var v6 *int             //指针
    var v7 map[string]int   //map key为string   value为int 
    var v8 func(a int) int
    
  • 公用var

    var (
        v1 int
        v2 string
    )
    
变量初始化
var v1 int = 10 //正确方式1
var v2 = 10     //正确方式2,编译器可自动推导v2类型
v3 := 10        //正确方式3,编译器可自动推导v3类型
  • 出现在:=左侧的变量不应该是已经被声明过的,否则会导致编译错误
变量赋值
  • 声明后赋值
    • var v10 int
    • v10 = 123
  • 多重赋值
    • 交换变量值:i,j = j,i
匿名变量
  • 不用的返回值可用_代替。

常量

  • 常量可以是数值类型(整型、浮点型、复数),布尔类型、字符串类型。
字面常量
  • 无类型
    • 只要这个常量在相应类型的值域范围内,就可以作为该类型的常量。
    • 如-12可以赋值给int、uint、int32、int64、float32、float64、complex64、complex128等类型的变量。
常量定义
  • 通过const关键字定义常量。
    • const Pi float64 = 3.14159265358979323846
    • const (
      size int64 = 1024
      eof = -1
预定义常量
  • iota

    const (//iota被重设为0 
        c0 = iota //c0 == 0 
        c1 = iota //c0 == 1 
        c2 = iota //c0 == 2
    )
    
  • true、false

枚举
  • go中没有enum,可使用const实现枚举。

    const(
        Sunday = iota
        Monday
        Tuesday
    )
    

类型

  • 对于常规的开发来说,用int和uint就可以了,没必要用int8之类明确指定长度的类型,以免导致移植困难。
  • 基础类型
    • bool
    • int8、byte、int16、int、uint、uintptr
    • float32、float64
    • complex64、complex128
    • string
    • rune:字符
    • error
  • 复合类型
    • pointer
    • array
    • slice:切片
    • map:字典
    • chan:通道
    • struct:结构体
    • interface
  • bool变量只能用true、false,不支持0、1,不支持类型转换。
  • int与int32在Go中为2种不同类型,不做自动类型转换,但可强制转换。
  • 左移*、右移/、2变量异或^、1变量取反^、与&、或|
  • 浮点数比较:浮点数不是一种精确的表达方式,不可像整型那样直接用==来判断是否相等。

    import "math"
    //p为用户自定义的比较精度,如0.00001
    func IsEqual(f1, f2, p float64) bool {
        return math.Fdim(f1, f2) < p
    }
    
  • 字符串的内容可以用类似于数组下标的方式获取,但与数组不同,字符串的内容不能在初始化后被修改。
  • len(s)获取字符串长度,x+y拼接字符串,s[i]取字符。
  • 字符串遍历

    • 按字节遍历(有中文时不方遍)

      n := len(str)
      for i := 0; i<n; i++ {
      }
      
    • 按Unicode字符遍历

      for i, ch := range str{
          fmt.Println(i, ch)   //ch类型为rune
      }
      
  • 字符类型
    • byte:代表UTF8字符串的单个字节的值
    • rune:代表单个Unicode字符
  • 数组
    • 数组长度在定义后不可更改
    • 数组长度:len(arr)
    • 定义方法
      • [32]byte //长度为32的数组,每个元素为一个字节
      • [2*N] struct { x, y int32 } //复杂类型数组
      • [1000]*float64 //指针数组
      • [3][5]int //二维数组
      • [2][2][2]float64 //等同于2)
    • 数组为值类型:所有值类型在赋值和作为参数传递时都产生一次复制动作。函数内操作的只是所传入数组的一个副本。
  • 数组切片
    • 创建切片
      • 基于数组创建切片
        • var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
        • var mySlice []int = myArray[:5]
      • 直接创建make
        • mySlice1 := make([]int, 5) //容量5,初始值0
        • mySlice3 := []int{1, 2, 3, 4, 5} //创建并初始化
    • cap()为数组切片分配的空间大小,len()为数组切片中当前所存储的元素个数
    • 向切片尾端添加元素
      • mySlice = append(mySlice, 1, 2, 3)
    • 将一个切片添加到另一切片尾端,注意不能少了...
      • mySlice2 := []int{8, 9, 10}
      • mySlice = append(mySlice, mySlice2…)
    • 基于切片创建新的切片
      • oldSlice := []int{1, 2, 3, 4, 5}
      • newSlice := oldSlice[:3]
    • 切片间复制
      • slice1 := []int{1, 2, 3, 4, 5}
      • slice2 := []int{5, 4, 3}
      • copy(slice2, slice1) //只会复制slice1的前3个元素到slice2中
      • copy(slice1, slice2) //只会复制slice2的前3个元素到slice1的前3个位置
  • map
    • 一堆键值对的未排序集合。
    • 变量声明
      • var myMap map[string] PersonInfo //PersonInfo为value的类型
    • 创建
      • myMap = make(map[string] PersonInfo)
      • myMap = map[string] PersonInfo{
        "1234": PersonInfo{"1", "Jack", "Room 101,..."},
        }
        
    • 元素赋值
      • myMap[“1234”] = PersonInfo{“1”, “Jack”, “Room 101,…”}
    • 元素删除
      • delete(myMap, “1234”),key在map中可以不存在,但key本身不可为nil。
    • 元素查找
      • value, ok := myMap[“1234”]
      • if ok { //找到了
      • //处理找到的value
      • }

流程控制

条件语句 if-else
选择语句 switch-case
  • 条件表达式不限制为常量或者整数
  • Go语言不需要用break来明确退出一个case
  • 只有在case中明确 加fallthrough关键字,才会继续执行紧跟的下一个case;
循环语句 for
  • Go中没有while
  • break可选择中止哪个标签处的循环
  • continue
跳转语句 goto

函数

函数定义
package mymath
import "errors"

func Add(a int, b int) (ret int, err error) {
    if a < 0 || b < 0 {
        err= errors.New("Should be non-negative numbers!")
        return
    }
    return a + b, nil //支持多重返回值
}
函数调用
  • 小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用。
  • 使用函数

    import "mymath"
    c := mymath.Add(1, 2)
    
不定参数
  • 固定类型的不定参数

    func myfunc(args ...int) {
        for _, arg := range args {
            fmt.Println(arg)
        }
    }
    
  • 任意类型的不定参数

    func Printf(format string, args ...interface{}) {
    }
    
多返回值
匿名函数与闭包
  • 匿名函数后跟参数列表表示函数调用

    func(ch chan int) {
        ch <- ACK
    } (reply_chan)
    
  • Go的匿名函数是一个闭包。

错误处理

error
defer
  • defer例子

    srcFile, err := os.Open(src)
    if err != nil {
        return
    }
    defer srcFile.Close()
    
  • 可使用匿名函数来defer

    defer func() {
        //复杂的清理工作
    } ()
    
  • 一个函数中可以存在多个defer语句,因此需要注意的是,defer语句的调用是按先进后出的原则, 最后一个defer语句将最先被执行。只不过,当你需要为defer语句到底哪个先执行这种细节而烦恼的时候,说明你的代码结构可能需要调整一下了。

panic()与recover()
  • func panic(interface{})

    • 函数执行过程中调用panic,函数立即停止执行,但defer语句还会正常执行,之后函数将返回到调用函数,并导致逐层向上执行panic流程,直至所属的goroutine中所有正在执行的函数被终止。
    • 例子
      • panic(404)
      • panic(“network broken”)
      • panic(Error(“file not exists”))
  • func recover() interface{}

    • recover用于终止错误处理流程。一般recover在defer函数中执行以有效截取错误处理流程。goroutine发生异常后没有调用recover的话会导致goroutine所属进程打印异常信息后直接退出。

面向对象编程

类型系统

  • 在Go语言中,你可以给任意类型(包括内置类型,但不包括指针类型)添加相应的方法。
  • Go语言和C语言一样,类型都是基于值传递的。要想修改变量的值,只能传递指针。
值语义和引用语义
  • Go语言中的大多数类型都基于值语义:byte、int、bool、float32、string、array、struct、pointer
  • 数组也是值引用
    • var a = [3]int{1,2,3}
    • var b = a
    • 修改b数组对a没有任何影响
    • 数组赋值时,将内容完整复制
  • 数组也可使用引用赋值
    • var b = &a
    • 修改b后a也同步修改
    • 赋值后,b的类型是 *[3]int,而不是[3]int
  • 数组切片内部是指向数组的指针,可改变所指向的数组元素。
  • map本质上是一个字典指针;channel与map类似,本质上是一个指针。
结构体
type Rect struct {
    x, y float64
    width, height float64
}

func (r *Rect) Area() float64 {
    return r.width * r.height
}

初始化

  • 在Go语言中,未进行显式初始化的变量都会被初始化为该类型的零值,例如bool类型的零值为false,int类型的零值为0,string类型的零值为空字符串。
    • rect1 := new(Rect)
    • rect2 := &Rect{}
    • rect3 := &Rect{0, 0, 100, 200}
    • rect4 := &Rect{width: 100, height: 200}
  • Go语言中没有构造函数的概念,对象的创建通常交由一个全局的创建函数来完成,以NewXXX来命名,表示“构造函数”。

    func NewRect(x, y, width, height float64) *Rect {
        return &Rect{x, y, width, height}
    }
    

匿名组合

  • Base类实现Foo()、Bar()方法

    type Base struct {
        Name string
    }
    func (base *Base) Foo() { ... }
    func (base *Base) Bar() { ... }
    
  • Foo类从Base类“继承”并改写Bar()方法

    type Foo struct {
        Base
        ...
    }
    
func (foo *Foo) Bar() {
    foo.Base.Bar()
    ...
}

可见性

  • 类型的成员变量、方法依靠大小写来实现可见性
  • 可访问性是包一级而不是类型一级。即类型Rect的area()方法,同一个package的其他类型也可访问到。

接口

  • goroutine和channel是支撑起Go语言的并发模型的基石,接口是Go语言整个类型系统的基石。
  • Go语言中,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口。类中的方法可以多于接口中的方法。
接口赋值
  • 将对象实例赋值给接口
    • 对象实例需实现接口中的所有方法
    • var b interfaceName = &a
  • 将一个接口赋值给另一个接口
    • 只要2接口拥有相同的方法列表(次序可不同),即为等同的,可相互赋值。
接口查询
  • 查询对象实例是否实现了某个接口

    var file1 Writer = ...
    if file5, ok := file1.(two.IStream); ok {
        ...
    }
    
类型查询
  • 查询接口指向的对象实例的类型。

    var v1 interface{} = ...
    switch v := v1.(type) {
        case int:     //现在v的类型是 int 
        case string:  //现在v的类型是 string
        ...
    }
    
  • 类型查询不常使用,常配合接口查询使用。
  • 利用反射也可进行类型查询:reflect.TypeOf()
接口组合
  • Go支持类型组合,也支持接口组合。
Any类型
  • 当函数可以接受任意的对象实例时,我们会将其声明为interface{},任何对象实例都满足空接口interface{}。

并发编程

并发基础

  • 多进程
  • 多线程
  • 基于回调的非阻塞/异步IO
    • 通过事件驱动的方式使用异步IO,使服务器持续运转,且尽可能地少用线程,降低开销,它目前在Node.js中得到了很好的实践。但是使用这种模式,编程比多线程要复杂,因为它把流程做了分割,对于问题本身的反应不够自然。
  • 协程
    • 本质上是一种用户态线程,不需要操作系统来进行抢占式调度, 且在真正的实现中寄存于线程中。
  • 线程间通信方式:共享内存。为保证共享内存有效性,采取了加锁来避免死锁或资源竞争。
  • 消息传递
    • 对线程间共享状态的各种操作都被封装在线程之间传递的消息中,这通常要求:发送消息时对状态进行复制,并且在消息传递的边界上交出这个状态的所有所有权。
    • 由于需执行复制操作,大多消息传递的实现在性能上并不优越。但线程中的状态管理会更简单。

协程

  • 线程和进程通常最多也不能超过1万个,而协程可轻松创建上百万个而不会导致系统资源衰间竭。

goroutine

  • goroutine为Go中的协程,Go语言标准库提供的所有系统调用操作(当然也包括所有同步IO操作),都会让出CPU给其他goroutine。
  • goroutine由Go运行时管理。
  • 函数前加go关键字,则会在一个新的goroutine中并发执行。若有返回值,返回值会被丢弃
  • main函数并不会等待其内的goroutine执行完毕,可能goroutine未来得及执行,main就退出了。

并发通信

  • 不要通过共享内存来通信,而应该通过通信来共享内存。
  • 消息机制:每个并发单元是自包含的、独立的个体,拥有自己的变量,不同并发单元间的变量不共享。
  • 并发单元间的输入、输出只有一种,就是消息。
  • Go消息通信机制为channel。

channel

  • 使用channel在2个或多个goroutine间传递消息。channel为进程内部的通信方式,通过channel传对象与函数执行时的参数传递一样,也可传指针。跨进程通信则可用Socket或HTTP。
  • channel为类型相关,即一个channel只能传一种类型的值。类型需在声明channel时指定。
声明、定义、读写
  • var ch chan int
  • value为channel类型的map
    • var m map[string] chan bool
  • 初始化channel
    • ch := make(chan int)
  • 数据写入channel
    • ch <- value
    • 向channel写入数据通常会导致程序阻塞,直到有其他goroutine从这个channel中读取数据。
  • 从channel中读取数据
    • value := <-ch
    • 如果channel之前没有写入数据,那么从channel中读取数据也会导致程序阻塞,直到channel中被写入数据为止。
select
    select {
        case <-chan1:
            //如果chan1成功读取到数据,则进行case处理语句
        case chan2 <- 1:
            //如果成功向chan2写入数据,则进行该case处理语句
        default:
            //如以上都没成功,则进入default处理流程
    }
- select用于处理`异步IO`问题。
- 每个case必为一个IO操作:面向channel的操作。
缓冲机制
  • 给channel带上缓冲,可达到消息队列的效果。
  • c := make(chan int, 1024)
  • 即使没有读取方,写入方也可以一直往channel里写入,在缓冲区被填完完之前都不会阻塞。
  • 可使用range读取数据

    for i := range c {
        fmt.Println("Received:", i)
    }
    
超时机制
  • select只要其中一个case已经完成,程序就会继续往下执行,而不会考虑其他case的情况。
  • Go语言没有提供直接的超时处理机制,但可利用select机制实现超时处理。

    //首先,我们实现并执行一个匿名的超时等待函数
    timeout := make(chan bool, 1)
    go func() {
        time.Sleep(1e9) //待待1秒钟
        timeout <- true
    }()
    
    //然后我们把timeout这个channel利用起来
    select {
        case <-ch:
            //从ch中读取到数据
        case <-timeout:
            //一直没有从ch中读取到数据,但从timeout中读取到了数据 
    }
    
channel的传递
单向channel
关闭channel
  • close(ch)
  • 判断channel是否关闭
    • x, ok := <-ch
    • ok为false表示ch已经被关闭

多核并行化

出让时间片

  • 在goroutine中主动出让时间片给其他goroutine。
    • runtine的Gosched()

同步

  • 使成功地用channel来作为通信手段,还是避免不了多个goroutine之间共享数据的问题。所以提供了资源锁方案。
同步锁
  • Go的sync包提供2种锁:sync.Mutex和sync.RWMutex。
    • Mutex
      • 一个goroutine获得Mutex后,其他goroutine只能等待这个goroutine释放该Mutex。
    • RWMutex
      • 读锁(RLock()):不可写,但多个goroutine可同时读。
      • 写锁(Lock()):独占,阻止任何其他goroutine(无论读和写)
  • 任何一个Lock()或RLock()均需要保证对应有Unlock()或RUnlock()调用与之对应,否则可能导致等待该锁的所有goroutine处于饥饿状态,甚至可能导致死锁。
全局唯一性操作
  • 全局角度只运行一次的代码,可使用Once类型保证全局的唯一性操作。

网络编程

  • IP层:Raw Socket
  • TCP/UDP层
  • HTTP、FTP、SMTP层

Socket编程

  • 传统Socket编程步骤
    • 建立Socket:socket()
    • 绑定Socket:bind()
    • 监听:listen()或连接connect()
    • 接受连接:accept()
    • 接收:receive()或发送send()
  • Go只需要调用net.Dial()即可。
Dial()函数
  • func Dial(net, addr string) (Conn, error)
    • TCP链接
      • conn, err := net.Dial(“tcp”, “192.168.0.10:2100”)
    • UDP链接
      • conn, err := net.Dial(“udp”, “192.168.0.12:975”)
    • ICMP链接
      • conn, err := net.Dial(“ip4:icmp”, “www.baidu.com”)
      • conn, err := net.Dial(“ip4:1”, “10.0.0.3”)
  • 支持的协议
    • tcp、tcp4、tcp6
    • udp、udp4、udp6
    • ip、ip4、ip6
  • 建立连接后,conn.Write()发送数据,conn.Read()接收数据。

HTTP编程

HTTP客户端
  • http.Get()

    resp, err := http.Get("http://example.com/")
    if err != nil {
        //处理错误...
        return
    }
    
    defer resp.Body.close()
    io.Copy(os.Stdout, resp.Body)
    
    • func (c Client) Get(url string) (r Response, err error)
    • 等价于http.DefaultClient.Get()
  • http.Post()

    resp, err := http.Post("http://example.com/upload", "image/jpeg", &imageDataBuf)
    if err != nil {
        //处理错误
        return
    }
    
    if resp.StatusCode != http.StatusOK {
        //处理错误
        return
    }
    // ...
    
    • func (c Client) Post(url string, bodyType string, body io.Reader) (r Response, err
      error)
  • http.PostForm()

    resp, err := http.PostForm("http://example.com/posts",url.Values{"title":{"article title"}, "content": {"article body"}})
    if err != nil {
        //处理错误
        return
    }
    // ...
    
    • 实现了标准编码格式为application/x-www-form-urlencoded的表单提交
    • func (c Client) PostForm(url string, data url.Values) (r Response, err error)
  • http.Head()
    • resp, err := http.Head(“http://example.com/“)
    • func (c Client) Head(url string) (r Response, err error)
  • (*http.Client).Do()

    req, err := http.NewRequest("GET", "http://example.com", nil)
    // ...
    req.Header.Add("User-Agent", "Gobook Custom User-Agent")
    // ...
    client := &http.Client{ //... }
    resp, err := client.Do(req)
    // ...
    
    • func (c Client) Do(req Request) (resp *Response, err error)
  • http.Get()、http.Post()、http.PostForm()和http.Head()方法其实都是在http.DefaultClient的基础上进行调用的,比如http.Get()等价于http.DefaultClient.Get()。
  • 自定义http.Client

    type Client struct {
        //Transport用于确定HTTP请求的创建机制
        //如果为空,将会使用DefaultTransport
        Transport RoundTripper
    
        //CheckRedirect定义重定向策略
        //如果CheckRedirect不为空,客户端将在跟踪HTTP重定向前调用该函数
        //两个参数req和via分别为即将发起的请求和已经发起的所有请求,最早的已发起请求在最前面。
        //如果CheckRedirect返回错误,客户端将直接返回错误,不会再发起该请求
        //如果CheckRedirect为空,Client将采用一种确认策略,将在10个连续请求后终止
        CheckRedirect func(req *Request, via []*Request) error
    
        //如果Jar为空,Cookie将不会在请求中发送,并会在响应中被忽略
        Jar CookieJar
    }
    
HTTP服务端
  • 处理HTTP请求
    • net/http 包提供的http.ListenAndServe()方法
    • func ListenAndServe(addr string, handler Handler) error
  • 处理HTTPS请求
    • net/http 包还提供 http.ListenAndServeTLS()方法
    • func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error
      • certFile对应SSL证书文件存放路径
      • keyFile对应证书私钥文件路径。
      • 如果证书是由证书颁发机构签署的,certFile参数指定的路径必须是存放在服务器上的经由CA认证过的SSL证书。

RPC编程

  • RPC协议构建与TCP或UDP,或者是HTTP之上。
Go中的RPC支持与处理
  • RPC服务端,可将一个对象注册为可访问的服务,之后该对象的公开方法就能够以远程的方式提供访问。一个 RPC服务端可以注册多个不同类型的对象,但不允许注册同一类型的多个对象。
  • func (t T) MethodName(argType T1, replyType T2) error

JSON处理

编码为JSON格式
  • func Marshal(v interface{}) ([]byte, error)
  • b, err := json.Marshal(gobook)
解码JSON数据
  • func Unmarshal(data []byte, v interface{}) error
  • var book Book
  • err := json.Unmarshal(b, &book)
解码未知结构的JSON数据
  • var r interface{}
  • err := json.Unmarshal(b, &r)
  • r将会是一个键值对的map[string] interface{}结构
  • gobook, ok := r.(map[string]interface{})
JSON的流式读写
  • encoding/json 包还提供Decoder和Encoder两个类型,用于支持JSON数据的流式读写,并提供NewDecoder()和NewEncoder()两个函数来便于具体实现
    • func NewDecoder(r io.Reader) *Decoder
    • func NewEncoder(w io.Writer) *Encoder

网站开发

最简单的网站程序
package main

import (
    "io"
    "log"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request){
    io.WriteString(w, "Hello, world!")
}

func main() {
    http.HandleFunc("/hello", helloHandler)
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err.Error())
    }
}

安全编程

工程管理

代码风格

命名
  • 变量、常量、全 函数、结构、接口、方法:任何需要对外暴露的名字必须以大写字母开头,不需要对外暴露的则应该以小写字母开头。
  • 拥护驼峰命名法而排斥下划线法。
排列
  • 约定了代码块中花括号的明确摆放位置。
代码格式代
  • go fmt hello.go
  • 若执行 go fmt,则格式化当前目录下所有代码。

远程import

import (
    "fmt"
    "github.com/myteam/exp/crc32"
)

开发工具

进阶话题

反射

  • 反射的代码可读性差,如非必要,不推荐使用。
  • Type与Value

    var reader io.Reader
    reader = &MyReader{"a.txt"}
    
    • Type:被反射的变量本身的类型信息
    • Value:被反射的变量实例本身的信息
    • Type为io.Reader,Value为MyReader{“a.txt”}
  • 获取类型信息

    package main
    
    import (
        "fmt"
          "reflect"
      )
    
    func main() {
        var x float64 = 3.4
        fmt.Println("type:", reflect.TypeOf(x))
    }
    
    • reflect.TypeOf()
    • v := reflect.ValueOf(x)、v.Type()、v.Float()

goroutine机理

协程
  • 能够在单一的系统线程中模拟多个任务的并发执行。
  • 在一个特定的时间,只有一个任务在执行,即并非真正地并行。
  • 被动的任务调度方式,即任务没有主动抢占时间片的说法。当一个任务正在执行时,外部没有办法中止它。要进行任务切换,只能通过由该任务自身调用yield()来主动出让CPU使用权。
  • 每个协程都有自己的堆栈和局部变量。
  • 协程都包含3种运行状态:挂起、运行和停止。

Kommentare: