「Go语言学习笔记」学习笔记

Posted by 小石匠 on 2020-06-19

书籍豆瓣链接:《Go语言学习笔记》

开始学习时间:

预计完成时间:

实际完成时间:

第1章 概述

defer FILO

结构体匿名嵌入其他类型

interface{} 接受任意对象

func (x *X) inc() {} 名称前参数是receiver

第2章 类型

变量

变量自动初始化二进制零值

:= 简短赋值,同作用域退化赋值,不同作用域定义并显式初始化

x, y = 多变量首先计算出所有右值,再依次赋值

命名

_ 可做左值,无法读取内容

常量

未使用局部变量编译错误,未使用常量没事

常量组某行不指定类型和值,与上一行非空常量右值相同

itoa 值根据下标计算

常量不分配空间,不可寻址

基本类型

引用类型

slice channel map(scm)

new 分配零值内存,返回指针

make([]int, 0, 10) make会调用目标类型创建函数,内存分配并初始化,引用类型必须使用make

类型转换

强制要求使用显示类型转换

转换的目标类型是指针,单向通道或无返回值函数,必须使用括号

第3章 表达式

运算符

二元操作符 除位移,两个操作数类型必须相同

++ – 只能后置,是语句不是表达式

指针支持相等运算,不能做加减和类型转换

初始化

{} 复合类型初始化,使用{}

流控制

if switch 支持多语句,根据最右边的语句判断,如为空默认true

for i,x := range,i和x分别是下标和值

range会复制目标数据,如想操作元数据,可以用数组指针或切片类型

第4章 函数

定义

不支持同名函数重载

不支持命名嵌套定义

不支持默认参数

函数返回局部变量指针是安全的,通过逃逸分析决定在堆上分配内存

参数

不支持默认参数,不支持命名实参

都是值拷贝传递

指针参数会延长目标对象生命周期,可能导致分配到堆上

如果复制成本很高,或需要修改原对象,使用指针更好,否则尽可能使用不可变对象

变参 a …int复制的是切片,并不是底层数组

返回值

命名返回值和参数一样,当做局部变量使用,由return隐式返回

匿名函数

闭包是函数和其引用的环境的组合体

闭包通过指针引用环境变量,可能导致生命周期延长,甚至被分配到堆内存

闭包有延迟求值特性

延迟调用

直到当前函数执行结束前才被执行,常用于资源释放、接触锁定以及错误处理

多个延迟注册按FILO执行,可以想象一个调用栈,先进后厨

return和panic触发延迟调用

错误处理

panic会立即中断当前函数流程,执行defer,defer中panic不会影响后续defer

defer中recover可捕获并返回panic提交的错误对象,recover必须在defer中执行,需要转型才能获取具体信息

连续调用panic,仅最后一个会被recover捕获,recover之后panic可被再次捕获

第5章 数据

字符串

string采用uft8编码,是变宽字符序列,len返回字节数

string具有不变性,字符串拷贝和切片都需要分配新内存

`反引号定义不做转义的raw string

要修改string,需要转换为可变类型[]rune和[]byte

string接受到[]rune类型转换,将utf8编码的字符串解码为unicode字符数组

byte 占用1个节字,所以它和uint8类型本质上没有区别,它表示的是ACSII表中的一个字符

rune 4个字节,共32位比特位,所以它和uint32本质上也没有区别。它表示的是一个Unicode字符

数组

切片

slice引用底层数组,本身是个只读对象,x[low,high,cap]是半开区间

cap表示切片引用底层数组片段的真实长度,len=high-low表示可读的写元素数量

make([]T, len, cap)和make([]T, cap)[:len]创建一个容量cap的匿名数组变量,返回一个slice,底层数组初始化为零值

make允许运行时动态指定数组长度

slice代替数组传参可避免复制开销

字典

使用make或者初始化表达语句创建map,否则是nil map

map的value的成员不可直接修改

运行时会对字典并发操作进行检测,某个任务对字典写操作,其他任务就不能读,写或删除,否则panic

map要使用sync.RWMutex同步

结构

struct 只有在所有字段都可比较,才可以比较

可以使用指针直接操作struct里的字段,但不可以是多级指针

struct{}可用作channel元素类型,用于事件通知

长度为0的对象都指向runtime.zerobase

内存布局 struct个字段在相邻地址空间按定义顺序排列。引用类型、字符串和指针,struct内存中只包含头部(ptr,len,cap)。其他字段以及匿名字段都包含在struct中

第6章 方法

方法

方法和函数的区别在于,方法有receiver

receiver可以是基础类型或指针类型,如果一个类型本身是指针,不可以成为receiver

receiver是基础类型,调用时对象实例会被复制

receiver形参是T,实参是*T,编译器会隐式取变量地址

receiver形参是*T,实参是T,编译器会隐式解引用,取到指针实际变量

适合使用*T的情况:

  1. 修改实例状态
  2. 大对象减少复制成本
  3. 包含Mutex,避免因复制造成锁操作无效
  4. 其他未知情况

匿名字段

可以像访问匿名字段成员那样调用其方法

struct自身方法,可以override匿名字段方法

方法集

类型*T的方法集包括所有receiver T+ *T的方法

匿名嵌入*S,T方法集包括所有receiver S+ *S的方法

匿名嵌入S或*S,*T方法集包含所有receiverS+ *S的方法

Go倾向于组合优于继承,将模块分解成相对独立的更小单元,处理不同方面的需求,最后以匿名嵌入方式组合到一起,共同实现对外接口

表达式

第7章 接口

定义

Go接口实现机制很简洁,只要目标类型方法集合内包含接口声明的全部方法,就被视为实现了该接口,无须做显示声明

因此可以先实现类型,而后再抽象出所需接口

interface通常以er作为名称后缀

interface{}类似于跟类型object,可被赋值为任何类型对象

执行机制

interface包含两个指针,类型指针itab和值指针data,都为nil时,接口值是nil

1
2
3
4
5
6
7
8
9
10
type iface struct {
tab *itab // 类型信息
data unsafe.Pointer // 实际对象指针
}

type itab struct {
inter *interfacetype // 接口类型
_type *_type // 实际对象类型
fun [1]uintptr // 实际对象方法地址
}

将对象赋值给接口变量,会复制该对象,而且不可寻址是unaddressable的

解决方法是将对象指针赋给接口,那么接口内存储的就是指针的复制品

类型转换

.(type)类型推断可以将接口变量还原为原始类型,用来推断是否实现了某个更具体的接口类型

技巧

第8章 并发

并发的含义

并发与并行

并发 逻辑上具备同时处理多个任务的能力

并行 物理上在同一时刻执行多个并发任务

协程概念

协程在单个线程上通过主动切换实现多任务并发,减少阻塞和线程切换开销,本质上还是串行的

进程、线程和协程

多进程 用来实现分布式和负载均衡,减轻单进程垃圾回收压力

多线程 LWP抢夺更多的处理器资源

协程 用来提高处理器时间片利用率

python由于GIL,只能并发不能并行,使用多进程+协程架构

并发模型

  • Actor模型

    每一对Actor之间,都有一个“MailBox”来进行收发消息。消息的收发是异步的

  • CSP模型

    Communicating Sequential Process, 通信序列进程

    使用定义的channel进行收发消息。消息的收发是同步的(也可以做成异步的,但是一个有限异步)

go routine

关键字go并非执行并发操作,而是创建一个并发任务单元,添加到系统队列中

每个go协程有自己的协程栈,初始大小2kb(线程栈MB级)

go协程没有优先级,无法获取编号,没有局部存储TLS,返回值也会被丢弃

runtime参数

GOMAXPROCS 设置线程数,默认与CPU核数相同

NumCPU 机器CPU核数

Local Storage

使用map做局部存储容器,建议做同步处理,因为会做并发读写检查

routine调度

wait 进程退出不会等待任务结束,可用channel或sync.WaitGroup

Gosched 释放线程去执行,当前任务被放回队列

Goexit 终止当前任务,运行时确保所有已注册延迟调用被执行,期间不会引发panic。main中调用,会等待其他任务结束,然后让进程直接崩溃

通道

Go允许使用全局变量、指针、引用类型这些非安全内存共享操作

Go鼓励使用CSP,以通信代理内存共享

同步和异步

make(chan T) 必须要有配对操作的goroutine出现,否则加入等待队列,直到另一方出现后唤醒

make(chan T, N) 发送方要有空槽可写,接收方要有数据可读或者管道关闭,否则加入等待队列

cap() len() 返回缓冲区大小和当前已缓冲数量

收发

close©和<-c配对,用于同步

x, ok:=<-c 根据ok判断通道是否关闭

for x:=range c {} 循环收消息,直到通道关闭

nil通道不能关闭,收发都会阻塞

closed通道,写panic,读返回以缓冲数据或零值,重复关闭panic

单向

c:=make(chan int)

var send chan<- int=c

var recv <-chan int=c

close操作不能用于接收端

选择

select语句,随机选择一个可用通道做收发操作,break退出

模式

性能

将发往通道的数据打包,减少传输数据 make(chan [block]int, bufsize)

资源泄漏

goroutine处于发送或接收阻塞状态,但一直未被唤醒,垃圾回收器并不收集此类资源,会在等待队列里休眠,造成资源泄漏

使用runtime.GC强制垃圾回收也没用

同步

通道倾向于解决逻辑层次的并发处理架构,锁用来保护局部范围内的数据安全

sync.Mutex互斥锁,不支持递;sync.RWMutex读写锁

第9章 包结构

工作空间

导入包

组织结构

依赖管理

第10章 反射

类型

反射让我们能在运行期探知对象的类型信息和内存结构

Go对象头部没有类型指针,反射操作所需的全部信息都源自接口变量

面对类型时,Type表示真实类型(静态类型),Kind表示基础结构(底层类型)

Elem返回指针、数组、切片、字典(值)或通道的基类型,如果是其他类型会panic

reflect.ValueOf(a)返回一个Value结构体,Value获取对象实例

CanAddr和CanSet实际上读取的是Value的flag字段,ValueOf返回的对象都是false

使用Elem方法返回可寻址对象(CanAddr),Elem方法只接受kind为interface和ptr的a

能探知当前包和外包的非导出结构成员(CanAddr),但只能修改导出成员(!CanSet)

可以使用Inteface().(.T)进行类型推断

方法

1
2
3
v := reflect.ValueOf(&a)
m := v.MethodByName("xxx")
m.Call()

构建

1
2
3
func add(args []reflect.Value) (result []reflect.Value) {}
v := reflect.MakeFunc(v.Type, add)
太复杂,见书本

性能

第11章 测试

单元测试

性能测试

代码覆盖率

性能监控

第12章 工具链

第13章 准备

第14章 引导

第15章 初始化

Go引导、启动与初始化

第16章 内存分配

Go内存分配机制
Go内存模型

runtime,虚拟机的一种,一般指进程级别的虚拟机。运行时是指一个程序在运行(或者在被执行)的依赖

第17章 垃圾回收

Go垃圾回收机制

第18章 并发调度

Go并发调度

第19章 通道

Go通道channel 与 select

第20章 延迟

第21章 析构

第22章 缓存池