目录
go相关基础概念
声明
var,const,type,func,import,package
指针
变量的地址
一个指针指示值所保存的位置。不是所有值都有地址,所有的变量都有地址。
使用指针,可以在无须知道变量名字的前提下,间接读取或更新变量的值。
1 | x := 2 |
逃逸与gc
逃逸
局部变量与包变量(全局变量)
1 | var global *int |
gc
当变量不可达时,被回收。
根据包变量以及每个执行函数的局部变量作为追溯该变量路径的源头,不可达时,被回收。
赋值
多重赋值 允许几个变量一次性被赋值,在实际更新变量前,右边所有的表达式被推演。
从风格上考虑,若表达式较复杂,使用独立的多条语句更有利于阅读。
可赋值性
左边的变量和右边的值类型相同,则赋值语句合法。
可赋值性 根据类型不同有不同规则
nil可以被赋给任何接口变量和引用类型
常量有更灵活的可赋值性规则来规避显示的转换
== 和 != 可进行比较与可赋值性相关
类的声明
type 声明定义一个新的命名类型,如果与现有底层类型冲突,命名类型提供了一种方式来区分,避免混用
1 | type name underlying-type |
类型转换
对于每个类型T,使用 T(x)将x转换为T,具有相同的底层类型,或者可以被赋值。
数字类型的转换,字符串和一些slice类型的转换也是被允许。
初始化
包的初始化从初始化包级变量开始,在依赖已经解析完毕的情况下,根据依赖的顺序进行。
如果包有多个.go文件构成,初始化按照编译器接收顺序处理,go工具回在调用编译器前,将go文件进行排序
init函数
init()函数会在每个包完成初始化后自动执行,并且执行优先级比main函数高。init 函数通常被用来:
- 对变量进行初始化
- 检查/修复程序的状态
- 注册
- 运行一次计算
初始化顺序
- 初始化导入的包(递归导入)
- 对包块中声明的变量进行计算和分配初始值
- 执行包中的init函数
go导入包的副作用
Go要求非常严格,不允许引用不使用的包。但是有时你引用包只是为了调用init函数去做一些初始化工作。此时空标识符(也就是下划线)的作用就是为了解决这个问题。
1 | import _ "image/png" |
作用域
包级别,声明的顺序,和作用域无关,一个声明可以引用其自己或者其后的声明,使我们可以声明递归或者相互递归的类型和函数。
如果常量或者变量声明引用他自己,编译器会报错。
go基础数据类型
- 基础类型 basic type
- 聚合类型 aggregate type
- 引用类型 reference type
- 接口类型 interface type
基础类型
整数与浮点数
int/uint 在特定的平台上,大小与原生的有无符号整数相同
rune 类型是 int32 类型的同义
byte 是unit8的类型的同义
uintptr 类型仅仅用于底层编程,如go在与c程序库或操作系统的接口界面。
浮点型 转成整数,如int(3.2),趋零截尾(整数向下,负数向上)
进制 8进制以0开头,16进制以0x开头
浮点数分为 float32 和 float64,32精度丢失问题较为严重,不建议使用。
- Math.NaN() 其返回值 与自身不等。
复数
- complex64
- complex128
布尔运算
多条件时,左侧可直接返回结果,右侧不再进行计算
&& 与 || 同时存在时,&&先进行计算。
字符串
go默认字符集是utf-8
字符串不可变,s[0] = ‘t’ 编译失败
字符串不可变意味着两个字符串可以公用同一段底层内存,使得复制任何长度字符串的开销都很低廉。
rune类型
GO中String类底层由byte数组实现,中文字符在unicode中占2字节,在utf-8中占用3字节。
字符串长度为len([]rune(str)) 非 len(str)
- byte 等同于int8,常用来处理ascii字符
- rune 等同于int32,常用来处理unicode或utf-8字符
字符串基本操作包
- bytes 操作字节slice. 由于字符串不可更改性质,增量创建字符串,导致多次内存分配与复制,因此使用 bytes.Buffer
- strings
- strconv 类型转换
- unicode 判别文字符号值得函数。如:IsDigit,IsLetter,IsUpper,isLower
strconv常用方法
1 | strconv.FormatInt(int64(x),2) // 2进制输出x |
常量
常量生成器iota
1 | const ( |
复合数据类型
数组
数组长度固定,在go中很少直接使用
slice长度可以增加或者缩短,使用场景多
- go中,数组为值传递
数组定义
1 | var a [3]int |
数组得初始化
1 | type Currency int |
没有指定值得索引位置的元素默认被赋予数组元素类型得零值
1 | r := [...]int{99:-1} |
其余位置得值都为0
slice
slice的属性: 指针,长度和容量. 通过go内置的len和cap可以返回slice的长度和容量
初始化: []T
slice的创建与截取:
1 | make([]T,len) |
- append 方法可能会造成slice扩展,因此内存地址可能会改变,需要再次传值
s = append(s,12).slice并不是纯引用类型 - 如果slice的引用超过了原slice的cap(s),则会宕机,如果只是超过了 len(s)最终slice只是比原slice长
slice容量的增长
slice.go(slice源码)- 如果新的 slice 大小是当前大小2倍以上,则大小增长为新大小
- 否则循环以下操作:如果当前slice大小小于1024,按每次 2 倍增长,否则每次按当前大小 1/4 增长,直到增长的大小超过或等于新大小。
- append 会开辟新的内存地址,将原slice的值赋给新slice的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27// 源码片段
func growslice(et *_type, old slice, cap int) slice {
...
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
...
}map
简单操作如果map的Key不存在,则返回默认的0值,即 0,ages[“bob”] == 01
2
3
4
5
6
7ages := map[stirng]int{
"alice":18
"helxo":24
}
ages := map[k]v
ages["alice"] = 18
delete(map,K)
结构体
结构的变量靠首字母大小写判断是否允许外部读写,若为大写,则可访问。
大的结构体用指针传值给函数
JSON
1 | // 将go转成json |
函数
go的闭包
延迟语句defer
函数和参数表达式在语句之前进行求值。
宕机与恢复
panic/recover
1 | func main(){ |
方法
特殊的函数
与函数的区别,方法绑定在变量上。
接口
goroutine和通道
goroutine
go中并发执行的活动称为goroutine
main函数返回,所有的goroutine都将暴力结束。没有程序化的方法让一个goroutine停止另一个goroutine
有办法和goroutine通信来让其自己停止
通道
通道可以让一个goroutine发送特定值到一另一个goroutine的通信机制。
每个通道是一个具体类型的导管,叫做元素类型
一个int类型的元素的通道写法如下:
1 | ch := make(chan int) // ch的类型是 chan int |
类似map,也是通过make创建数据结构的引用。
通道的操作:
- send –> ch <- x
- receive –> x = <-ch, <- ch // 接收语句,丢弃结果
- close –> close(ch)
关闭后的发送操作会导致宕机
无缓冲的通道
无缓冲的通道上的发送操作会阻塞,直到另一个goroutine在对应的通道上执行接收操作,这时,传送完成,两个goroutine都可以继续执行。想法,如果接收操作先执行,接收方goroutine将阻塞,直到另一个goroutine在另一个通道上发送一个值。
无缓冲的通道也被称为同步通道
1 | func main(){ |
管道
通道可以用来链接goroutine,这样一个的输出是另一个的输入。这个叫管道pipeline。
range语法可以在管道上迭代
1 | for { |
管道的关闭: 试图关闭一个已经关闭的通道会导致宕机。
单向通道
1 | chan<- int // 只允许发送的通道 |
缓冲通道
缓冲通道有个元素队列,队列的最大的长度在创建时指定
1 | ch := make(chan string,3) |
缓冲通道上的发送操作在队列的尾部插入一个元素,接收操作从队列的头部移除一个元素。
如果通道满了,发送操作会阻塞所在的goroutine直到另一个goroutine对它进行接收操作留出可用的空间。反过来,如果通道是空的,执行接收的goroutine会阻塞,直到另一个goroutine在通道上发送数据。
1 | ch <- 'A' |
go内置cap和len 方法可以分别获取通道缓冲区的大小和当前通道内的元素个数
goroutine的泄露
goroutine被阻塞住之后不会被回收
1 | func mirroredQuery() string { |
以上代码中,慢的两个request会被阻塞住,它们发送响应结果到通道时,没有goroutine接收。
多路复用
select
goroutine取消
取消机制:
- 创建一个取消通道,不发送任何值,只是在取消时,发送关闭通知。(在它被调用时,检测或轮询取消状态)
- 创建一个标准输入goroutine,通过关闭done通道来广播取消事件
共享变量实现并发
互斥锁sync.Mutex
go 的互斥锁是不可重入锁
读写互斥锁sync.RWMutex
内存同步
- 在缺乏显示同步的情况下,编译器和cpu在能保证美国个goroutine都满足串行一致性的基础上可以自由地重排访问内存的顺序。
- 将变量的读写放到单一goroutine内,保证执行顺序一致
如下段代码,并发执行Icon并不会导致重复执行loadIcon
1 | var icons map[string]image.Image |
延迟初始化sync.Once
1 | var loadOnce sync.Once |
once 包含了一个bool变量和一个互斥量,bool变量记录初始化是否完成。
互斥量负责保护这个bool变量和客户端的数据结构。
每次调用once.do
时,会先锁定互斥量并检查里边的bool变量。
通过互斥变量的同步保证内存可见性。
竞态检测器
go语言运行时和工具链包含了一个精致并易于使用的动态分析工具:竞态检测器(race detector)
简单地将-race
命令行参数加到go build, bo run, go test
命令后。
- 它会让编译器为你的应用或测试构建一个修改后的版本,这个版本有额外的手法用于高效记录在执行时对共享变量的所有访问,以及读写这些变量的goroutine标识。
- 修改后的版本还会记录所有的同步事件,包括
go
,通道操作,(*sync.Mutex).Lock调用,(*sync.Waitgroup).Wait调用
goroutine与线程的差异
goroutine 与 os 线程的差异
可增长的栈
- os线程有个固定大小的栈内存(2MB)
- goroutine 栈的初始大小只有2kb,最大为1gb
- 栈用于存放正在执行或者临时暂停的函数中的局部变量。
goroutine调度
OS线程由OS内核调度。每隔几毫秒,一个硬件时钟发送中断到CPU,cpu调用调度器
内核函数。这个函数暂停当前正在运行的线程,把它的寄存器信息保存到内存中,查看线程列表并决定接下来运行哪一个线程,再从内存恢复线程的注册表信息,最后执行选中的线程。因为os线程由内核来调度,所以控制权限从一个线程到另外一个线程需要一个完整的上下文切换(context switch):即保存一个线程的状态到内存,在恢复另外一线程的状态,最后更新调度器的数据结构。
GO运行时包含一个自己的调度器,这个调度器使用一个成为 m:n 调度的技术(可以复用/调度m个goroutine到n个OS线程),go调度器与内核调度器的工作类似,但go调度器只需关心单个go程序的routine调度问题。
与操作系统不同的是,go调度器不需要由硬件时钟来定期触发,而是由go语言结构来触发。
goroutine不需要切换到内核语境,因此开销成本比内核线程低很多。
GOMAXPROCS
Go调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来运行GO代码,默认值是CPU的数量。
正在休或者正被通道通信阻塞的goroutine不需要占用线程。
可以使用GOMAXPROCS
环境变量或者runtime.GOMAXPROCS
函数来显示控制这个参数。
包和go工具
空导入
如果导入的包没有在文件中引用,就会产生一个编译错误,但是有时候必须导入一个包,这仅仅是为了利用其副作用:对包级别的变量执行初始化表达式求值,并执行它的init函数
1 | import _ "image/png" // 注册png解码器 |
go工具
gotool 用来下载,查询,格式化,构建,测试及安装代码包go help
查询
工作空间的组织
大部分用户必须进行唯一的配置是GOPATH
环境变量,它指定工作空间的根,当需要切换到不同的工作空间时,更新GOPATH
就可