基础
#
特点
易工程化
简单性而不方便性,避免工程复杂性乘法增长 # 某部分变复杂,增加其他部分的复杂性(功能、选项、配置)
没有动态库, 没有泛型, 没有继承, 没有异常, 没有宏,没有注解,没有线程局部存储
类型系统,无类型风格
自然方式工作
不显式初始化和隐式构造函数
集合直接持有元素
标准库避免配置和解释 # 自带电池
项目结构简单
编译检查代码格式
csp(communicating sequential process)并发,变长栈运行轻量线程
编译为本地机器码 # 像c一样,所以又叫类c语言
编译快
引用包名在头
包依赖有向无环,可独立和并行编译
目标文件包含依赖包信息
强静态类型
有gc
变长栈,最小2kb, 最大1GB
大厂支持
历史
2007年设计,受影响于Alef(CSP系列), Oberon-2(ALGOL60, Modula-2系列), C
# 目的解决google许多复杂性激增的软件系统
2009年发布, 作者是Robert Griesemer, Rob Pike, Ken Thompson
2012年1.0
并发编程特点
语言层面关键字
例程
流程控制: csp # channel为一等公民
通信方式: promise-future, channel, event
高效调度模型(调度器,资源占用小)
O(1)的调度
一进程可支撑上百万例程,5kib/goroutine的开销,
变长栈存goroutine
编译
CGO_ENABLED=0
# 静态链接,不跨平台
初始化
包级别初始化在main前
局部变量在函数执行时
配置
GOROOT # go安装目录
GOPATH # 包目录, 默认要有go的bin目录
GOBIN # 当前bin目录
GO15VENDOREXPERIMENT # 依赖目录
GOOS # 指定操作系统, 如android, linux, darwin, windows
GOARCH # 处理器架构,如amd64, 386, arm
命令
#
go
help
importpath # 说明 指定代码托管网站版本协议
gopath # vendor怎么使用
list # go list 说明
version
env # 打印go环境信息
run # 编译并运行
-race # 检查运行中的竞态冲突并报告
build # 库被舍弃,main包编译成二进制执行文件, 会检测mod
go build gopl.io
go build x.go
-race
-i # 编译到指定位置
install # 编译安装, 会检测mod
clean # 清理build产生的文件
-c # 清理.test文件
-i # 清理生成的可执行文件
-r # 包括依赖包的结果文件
doc
命令
go doc go/build
包
go doc html/template
包成员
go doc time.Since
方法
go doc http.ListenAndServe
fmt # 代码格式化
get # 下载依赖, 默认目录是GOPATH下的pkg。下载后自动install
go get gopl.io/... # ...通配
get gopl.io@2 # 指定mod版本号
-u # 更新到mod最新版本
-v # 查看进度
list # 列出指定代码包的信息
go list ... # ...通配
go list ...xml...
-json hash # 输出json格式完整信息
-f # 使用go模板
fix # 升级旧代码成新版本代码
vet # 检查静态错误
test
# go test -cover -args -config config_it.toml -test.run "TestA"
# 执行当前目录下所有_test.go结尾的文件
-race
-file # 可省略,测试单个文件, 如go test test_a.go a.go a.pb.go
## 测试单个文件需要引入原文件
-args # 运行时参数
-run TestFoo # 正则表达式匹配方法,NONE表示不匹配。如"^TestFoo", "F|F1"
-test.run "TestCreate" # 同上
-v # 每个测试用例的名称和时间
-bench=".*" # 正则匹配benchmark测试函数
-benchmem # 显示benchmark测试时内存分配
--cpuprofile=cpu.prof # 生成cpu分析文件,使用多个标记时(如cpu, mem), 一个类别会覆盖另一个。性能剖析启用时, go test不丢弃其临时可执行文件
--blockprofile=block.out # 生成阻塞分析文件
--memprofile=mem.prof # 生成内存分析文件
-c # 生成可执行的二进制文件,名为x.test,它用来生成状态图
-cover # 显示覆盖语句汇总信息
-coverprofile=c.out # 生成日志文件c.out,记录语句覆盖信息
-covermode=count # 语句覆盖信息不用bool而用count累加
tool
cover # 测试覆盖率工具使用方法
go tool cover -html=c.out # html分析c.out
pprof # 交互式访问概要文件
go tool pprof module1.test cpu.prof # 性能测试状态图, 参数是可执行文件和剖析日志
-test # 文本格式
-nodecount=10 # 限制输出10行
-web # 渲染有向图web显示
fix # 同go fix
vet # 同go vet
cgo # 生成能够调用c语言代码的go源码文件
compile
-help # 可传给编译器的参数
mod
init packageName1 # 生成go.mod
download # 下载mod
tidy # 下载缺少,删除多余
edit # 编辑go.mod
graph # 打印依赖图
vendor # 复制依赖到vendor
verify # 验证mod
why # 打印依赖原因
godoc # 提供html页面
-http=:6060 # 运行本地帮助网站
-analysis=type # 提供静态分析结果
-analysis=pointer
gofmt
golint # 检查风格
常用
#
go mod 配置
环境变量
GO111MODULE
off # 总关闭
on # 总开启
auto # 默认,有go.mod开启
路径
$GOPATH/pkg/mod # 保存多版本依赖, 被多项目引用
go.mod # 被go命令维护, 融入了go命令的各个模块
go.sum # 记录lock
依赖加载顺序
最新release tag
最新commit
命令
go mod vendor
代理
go env -w GOPROXY=https://goproxy.cn,direct
包升级
go list -m -u all # 检查可以升级的package
go get -u need-upgrade-package # 升级
性能测试
go test -bench=. --cpuprofile=cpu.prof --memprofile=mem.prof -config ../conf/config_lc.toml -test.run TestCreateType
覆盖率
go test -cover -args -config config.toml -test.run "TestCreate"
性能分析
go tool pprof service.test cpu.prof
go-torch -b cpu.prof
包管理
go list -m -u all
# 列可升级包
go list -u need-upgrade-package
# 升级可升级包
go get -u
# 升级所有依赖
工具
#
glide
#
介绍
包管理
目录
glide.yaml
glide.lock
main.go
subpackages
vendor
命令
glide
init
# 扫描代码目录,创建glide.yaml文件,记录所有依赖
删除glide.yaml中自己项目本身
get
# 安装并更新glide.yaml
--all-dependencies -s -v github.com/go-redis/redis#5.0.0
# --all-dependencies会更新subpackages
update
# 下载和更新glide.yaml中的所有依赖,放到vendor下
# 递归更新
install
# 依据glide.lock与glide.yaml文件安装特定版本
# glide.lock与glide.yaml不同步时,发出警告
up
# 更新依赖树,重建glide.lock文件
name
# 查看glide.yaml中依赖名称
list
# 依赖列表
help
--version
glide.yaml
package: .
import:
- package: github.com/go-redis/redis
version: 5.0.0
repo:git@github.com:go-redis/redis
常见问题
o-> cannot detect vcs
glide.lock或vendor依赖旧版本
清理glide.lock和vendor, 检查glide.yaml旧版本
glide.yaml子目录处理不完善
subpackages:
- cloudsql
glide mirror找不到包
glide mirror set a a --vcs git
# 改~/.glide/mirrors.yaml文件
o-> does not appear to be a git repository
加速服务没有项目
o-> glide up依赖不是最新
~/.glide/cache中缓存了旧版本
o-> cannot find package "." in
glide对非git协议自有域名处理歧义,子目录分析不准确
清理缓存
~/.glide/cache/src/包名
~/.glide/cache/info/包名
glide.yaml添加repo重定向及subpackages
package: github.com/grpc-ecosystem/grpc-gateway
repo: git@github.com:grpc-ecosystem/grpc-gateway.git
subpackages:
- internal
govendor
#
介绍
包管理
使用
go get -u -v github.com/kardianos/govendor
godev
#
# 依赖管理
gv
#
# 依赖管理
gvt
#
# 依赖管理
gvm
#
# 版本管理
命令
gvm
install go1.5
use go1.5
list
listall
implode
# 删除所有go版本和gvm本身
gore
#
# repl
go-torch
#
# 性能火焰图
go-torch -b cpu.prof
gf
#
-v/version
-h/help
init
build
gen # 生成模块
gen dao
run
swagger
pack
get
docker
mod
update
语法
#
包 # 路径引用,命名空间
不能循环依赖
main包 # 入口包, 产生可执行文件,也可当库导入
main() # 入口函数
包名最好匹配目录名 # 导入路径的最后一段
import
逐行import或import(多行)
import 别名 包路径
import "gopkg.in/yaml.v2" 忽略.v2, 包名为yaml
import _ "image/png" 空导入
可导入相对目录,以.或..开头
var和const
逐行或var(多行), const(多行)
包文件函数
init # 文件在编译前排序,按依赖顺序和文件名,也是init的调用顺序。init不能调用和引用
包依赖排序依次初始化
工作空间
src
bin # 编译后的执行文件
pkg # 编译后的包, 重复使用加快编译
vendor目录放本地依赖
文档注释影响编译
// +build linux darwin # linux和darwin才编译
// +build ignore # 任何时候都不编译
内部包 # 路径有internal的包, 只能被父目录导入
net/http/internal/chunked
注释
//或/**/
package前写文档注释,可出现在任何文件中,但一个包约定一个
doc.go约定为包的扩展文档注释
命名
字母或下划线开头,区分大小写, 不能用关键字
关键字: break, default, func, interface, select, case, defer, go, map, struct, chan, else
goto, package, switch, const, fallthrough, if, range, type, continue, for, import, return, var
首字母大小写决定可见性,大写表示其是导出的
go倾向短名称, 驼峰式命名, 缩写全大写或小写
操作符
优先级:
* / % << >> & &^ # 可加=, 如*=
+ - | ^ # 可加=, 如*=
== != < <= > >= # 基本类型都可比较
&&
||
+ - # 一元取正负
% # 只能整数,符号与被除数一致
/ # 整数除,去小数部分。溢出高位丢弃
& | ^ &^ # 按位独立
&^ # and not(位清空)右边1清左边为0
& # and
| # or
^ # xor, 前缀表示取反或按位取补(逐位取反)
<< >> # 注意,有符号数右移按符号位填补空位
声明定义
不能有无用的声明变量
var s string # 未初始化值默认零值,类型和表达式可省一个
var s string = "" # 不推荐
var s = "" # 不推荐
s := "" # 短变量声明
var i,j int # 自动引申变量类型
i, j := 0, 1
i, j := true, "a" # 类型可不一致
i, err := 0, e # err声明过(只检测本词法块,外层不算),声明i, 赋值err。:=要求至少一个声明
const # 编译时计算
枚举
type Weekday int
const (
Sunday Weekday = iota # 值为0
Monday
...
)
const (
i T1 = 1 << iota # 值为1, 下值为 1<<1
...
)
无类型常量
只有常量可以无类型
无类型常量存大数精度高于基本类型, 算术精度高于机器精度, 至少256位
可用于更多表达式,无需转换类型
分类
布尔: true
整数: 0
文字符号: '\u0000'
浮点数: 0.0
复数: 0i
字符串: ""
const (
_ = 1 << (10 * iota) # 下值为 1<<(10*1)
...
)
type # 类型声明
type A int # 底层共类型, 可赋值性,可比较性。可重写String方法让fmt打印时个性化
a = A(i) # 可赋值就可类型转换
func
变量生命周期
包级别是整个程序执行时间
局部变量声明时创建,直到不可访问
堆栈
逃逸变量(函数外保存地址)在堆,局部变量在栈 # 与变量创建方式无关(new与否), 逃逸需要一次额外内存分配。
赋值
x = 1
*p = 1
p.name = ""
m[key] = 1
+=
i++和i-- # 只能自成一行, 且不能++i, --i
_ # 忽略变量
a, b = b, a # 多重赋值
v, err = f()
v, ok = m[key] # map查询
v, ok = x.(T) # 类型断言
v, ok = <-ch # 通道接收
可赋值性:类型精准匹配,nil赋值任何接口变量或引用变量, 常量更易隐式转换赋值
==和!= 比较,两边要求是可赋值的
指针 # 不能运算
&获取地址
*获取指针
语句
变长参数
f(s...)
for
for i, j := 0, 1; i < n; i++ {}
for i < n {}
for {}
for index, value := range slice1{}
for key, value := range map1 {}
for range s{}
if
if i < n {} else if i < m {} else {}
if err := f(); err != nil {}
switch
switch { # 无标签(tagless), 相当于switch true,
case x > 0: # fallthrough可贯穿, 可用控制流标签
}
switch i := 0 {} # switch true
switch i++ { } # switch true
switch f() {} # switch true
switch i {
case 0:
case 1:
default: # default可在任何地方
}
switch t.(type) { # 类型匹配, 无匹配类型会panic, 不能用fallthrough
case nil:
case int, uint;
case bool:
case string:
default:
}
switch x := x.(type) {}
select # channel用select, 值用switch。一直等待直到匹配(default会直接匹配)。多情况匹配随机选择。不能用fallthrough
select {
case <-ch1:
case x := <-ch2:
case ch3 <- y:
default:
}
o-> 超时
select {
case <-time.After(10*time.Second):
case <-ch:
}
o-> 自发自接
for i := 0; i < 10; i++{
select {
case x := <-ch:
case ch <- i:
}
}
o-> 标签
c := make(chan struct{},2)
label1:
for {
select {
case c<- struct{}{}:
fmt.Println(1)
case <-c:
fmt.Println(2)
break # 无标签break跳出当前select块
# break label
# goto label2
# return
default:
fmt.Println(3)
}
}
label2:
...
控制流标签
break, continue, goto # 可标签化, 如break Label1
作用域
词法块:语法块(block)(大括号), 隐式块(未在大括号中的声明, 如if中)
全局块 # 内置
包级别 # 函数外的声明
文件级别 # 导入的包
局部
控制流标签作用域是外层函数
覆盖
x := 1
for {
x := x+1
if .. {
x := x+1 # 这里值的x是最外层x
}
}
if声明的变量(隐式词法块),else中可见
if v, err := f(); err != nil { # else中处理非err逻辑
return err
} else {
v.close()
}
包中声明作用域无顺序,可递归 # 常量、变量不可以引用自己
函数
字面量
var func() # 声明
func f(i int) int {}
func f(i, j int) (int, error){}
func f() (r int){}
func f(vals ...int) {} # 变长函数, 只放最后, vals是slice
f(vals...)
一等公民
函数签名,即函数类型,包括形参列表和返回列表, 命名不影响
未定义时为nil, 不可比较
值传递, 不能指定默认值
函数返回多值, 可return, 可做传入参数
返回值有命名,可祼返回(不在return后写参数)
错误
v, ok := f() # 错误只一种情况
v, err := f() # 错误信息会串联,避免首字母大写和换行
if err != nil {
return nil, err
}
匿名函数用字面量定义,包含外层词法环境(闭包)
递归要先声明,再赋值定义
重定义变量
for _, dir := range dirs() {
dir := dir # for块作用域变量共享位置,重定义dir每次一个位置
dirs = append(dirs, func(){ # 匿名函数引用外层变量,却不一定同步执行
os.RemoveAll(dir)
})
}
方法
字面量
func (t T) f(){} # t值传递, 方法名唯一, 方法名不能用字段名
func (t *T) f(){} # t引用传递
type Path []Point # 别名赋方法(不能是指针和接口), 可覆盖原类型方法
func (p *Path)f(){}
方法可在任何类型(除指针和接口), 如函数
变量与变量指针都可直接调方法,编译器隐式取地址或取指针
P{1}.f() # 编译器不报错但运行出错, f()声明成引用传递, 但P{1}.时, 内存地址还未分配, 即还没有*P, 就无法调f()
有引用传递方法时,避免值传递方法,会产生多例
值为nil时可调方法
方法可赋值
f := t.f
f(t.f)
组合
结构体匿名成员方法可如属性般直接调用
匿名成员是引用时,多结构体可组合同一成员对象
多匿名成员方法冲突时,调用时编译报错
可给未命名结构体加方法
t = struct{
sync.Mutex
v int
}
t.Lock(); t.v++; t.Unlock()
接口
字面量
type I interface {
f()
}
type I2 interface { # 接口组合
I
}
v := t.(T) # 断言,失败panic
v, ok := t.(T) # 失败不panic, ok=false
隐式实现,方法匹配即可(接口即约定) # 鸭子
指针方法匹配接口,对接口赋值时要传指针
interface{}为空接口类型,可赋值任何类型
实现
除了接口的定义类型,还包含动态类型(Type) + 动态值(Value) # 编译时不知道,生成代码在运行时动态分发
零值,是动态类型和动态值都是nil
动态类型是nil, 指的是它为接口本身类型
i = nil会设置接口为零值
==和!=比较
动态值都为nil相等
动态类型和动态值都相等,才相等
i = new(T); i != nil
动态类型一致,动态值不可比较(如slice), 则panic # 非平凡
格式化输出%T拿到动态类型
断言
变定义类型, 动态类型和动态值不变
空接口断言总失败
断言成接口,使用公共功能
v := t.(I)
v.Common()
风格
强调功能相似性 # 子类型多态(subtype polymorphism)
联合再断言区分 # 可识别联合(discriminated union), 特设多态(ad hoc polymorhpism)
switch t.(type) {}
关键字
defer fn # 后进先出, return或panic后调用
defer func(i){...}(1)
return、出参赋值、defer顺序 # 先赋值,再defer,再return
func f() (i int) {
defer func(){
i++
}
return 0 # 相当于 i=0; i++; return
}
go fn
异常
panic()
日志包括值(interface{}), 调用栈
用interface{}自定义异常类型
runtime.Stack()查看栈,利用defer函数在栈清理前调用, 所以栈存在
终止当前goroutine, 外层goroutine不捕获
按函数调用栈依次中止函数并调defer, 最上层后程序异常退出
panic之后定义的defer不执行(声明不提前)
recover()
中止panic
在defer中(panic时只调defer)捕获panic对象,没有时为nil
捕获panic对象后, 捕获函数正常返回。要上抛就再手动panic()
o->
func Try(fn func(), handler func(interface{})) {
defer func() {
if err := recover(); err != nil {
handler(err)
}
}()
fn()
}
func main() {/
Try(func() {
panic("a")
}, func(e interface{}) {
print(e)
})
}
内置
#
零值 # 保障变量良好定义,没有未初始化变量
数字0, 布尔false, 字符串""
接口和引用类型(slice, 指针, map, channel, 函数)nil
复合类型其所有元素或成员零值
常量
true
false
iota
nil
比较
var s []int # 未初始化比较, ==nil
s = []int(nil) # 强转, ==nil
s = []int{} # 初始化比较, !=nil
基本类型
字面量
06 # 8进制
0x0a # 16进制
.1或1. # 小数点前后可省略
2.2e10 # 科学计数法
1 + 2i # 复数
字符串
"\\" # 转义
"\r" # 光标退到行首
"\b" # 光标退一字符
"\x1a" # 16进制表示位数据, 必2位,无法识别成unicode
"\212" # 8进制表示位数据, 必3位, 无法识别成unicode
"\u1234" # unicode, 16进制数字, 共4x4=16位
"\u12345678" # unicode, 16进制数字, 共4x8=32位
`` # 原生字符串, 回车被删除(换行符保留)
注意
会自动截断,如int i=127; i+1=-128; i*i=1
int # 平台原生整数大小,或该平台运算效率最高值, 多是int32
int8 # -128-127
int16
int32
int64
uint # 平台决定大小。无符号极少用于表示非负值,往往用于位运算或特定算术运算符,如位集,解析二进制,散列,加密
uint8 # 0-255
uint16
uint32
uint64
uintptr # 存放指针,大小不明确,底层编程
float32 # 运算会迅速累积误差, 正整数范围有限
float64
complex64 # float32构成
complex128 # float64构成
var x complex128 = complex(1,2)
x := 1 + 2i
bool
byte # uint8别名, 强调是原始数据
rune # int32别名, unicode码点(UTF-8), 下标取字符(非字节)
string # 认为是UTF-8编码的unicode, 不合理字节替换成方块(\uFFFD)。不可变(安全截取、共用), 下标取字节,越界宕机异常
和数组和slice一样操作
[]byte和string元素操作一致,只类型不同
互换
[]byte, []rune, string # 转换产生副本
error
聚合类型
数组
字面量
var q [3]int
q := [3]int{1,2,3}
q := [...]int{1,2,3} # 长度由元素个数决定
q := [...]int{0: 1, 3:2} # 指定索引元素值
数组是值传递
数组元素不可包含自己
默认元素为零值
不同长度不同类型,不能赋值
如果元素可比较,数组就可比较 # 深度比较
q1 < q2 # 字符串比较按字节字典排序
slice
字面量
q := []int{1,2,3} # 这里创建了slice, 指向了隐式创建的数组
q[0:1] # 左闭右开
q[:1]; q[1:]; q[:]
轻量级数据结构,用来访问数组的部分
零值是nil, 行为和slice一样,不用特殊判断
slice后标访问越界时,会自动扩展,越界超过数组长度+1时,会panic
append(arr[:i], arr[i+1:]...)删除元素, i为最后元素时, i+1不越界
不可比较, 只有写函数实现。只能和nil比较
因为slice的元素不是直接的
有可能包含它自身
同slice不同时间会拥有不同元素
如果slice可比较来做map键, map只对key做浅拷贝, slice需要深度比较, 所以要求slice元素不变
三元素 # 有自己的属性,不是纯引用类型,是聚合类型
指针: 指向slice在数组上起始位置
长度: slice长度
容量: 指针到数组结尾元素个数
map # key可nil, 取不存在key时, 得到value类型的零值。随机无序遍历
字面量
m := map[string]int{
"a":1,
}
a["b"]=2
不能获得地址,如&m["a"] # 因为map增长时,已有元素可能重新散列
迭代顺序随机 # key用维护排序, 散列算法健壮
零值是nil, 向nil map设置元素会panic
map[key1]没有时,获得零值
v, ok := m["a"] # 判断有无key
不可比较,只能和nil比较
key要求可比较,可以数组,不可以slice,可以自己映射成可比较类型
q := [2]int{}
m := map[[2]int]int{}
m[q] = 1
结构体
字面量
type T struct { # 结构体,
Name string `json:"name0,omitempty"` # 成员标签定义, opmitempty在零值时忽略
I1, I2 int
}
t := &T{"a"} # 顺序易出错, 用于明显的小结构。未指定成员为零值
t := &T{
Name: "a",
}
(*t).Name = "a"
t.Name = "a" # .可以用于指针
struct{} # 空结构体,没有长度,无信息。
type T1 struct{ # 匿名成员
T
T2
*T3
Name1 string
}
t1 := T1{
T: {
Name: "a"
}
}
首字母大写可导出
属性类型不可自己,但可自己指针
结构体零值由成员零值组成 # 希望结构体方法中处理零值成一个自然的值,如sync.Mutex
成员可比较,结构体实例可比较, 可作map key
匿名成员(组合)
点号访问可跨入(语法糖),访问匿名成员属性和方法
t1.Name; t1.T.Name
不能有相同类型的匿名成员
不可导出类型的匿名成员,内部成员不影响,但匿名成员本身不可见
引用类型
Type
IntegerType
FloatType
ComplexType
chan
ch := make(chan string)
var cin chan<- string
var cout <-chan string
ch <- ""
<-ch
接口类型
error
Error()
命名类型
type
结构体
type Point struct {
X, Y int
}
函数
make()
make([]int)
make(map[string]int)
make(chan int)
delete()
delete(m, "a") # 删除map元素, 没key不报错返回零值
len()
len(ch) # 当前缓冲个数
cap()
cap(ch) # 缓冲区容量
new() # 创建指定类型变量,初始化为零值,返回地址。不带任何信息且是零值(struct{}和[0]int)的类型, new出的地址不同(从前相同)
t := new(T)
append() # 操作slice
先检查原容量,容量够修改原数组元素,不够创建新数组(容量扩一倍)复制元素, 返回新slice
所以append()最好赋值给原slice
s1 := append(s1, s2...)
copy() # slice或string元素复制
close() # channel中用
complex() # 创建复数对象
real() # 获取复数的实部
imag() # 获取复数的虚部
panic()
recover()
反射
谨慎使用
脆弱,能导致编译报错的写法,反射中都对应panic,执行时才知道
降低自动重构和分析工具的安全性与准确度,反射对类型操作无法静态检查
反射慢1-2个数量级(实测20位左右), 适合测试用,不适合关键路径上用
unsafe
值在内存中对齐,计算更高效。结构体用内存间隙来对齐,占空间比元素之和更大
结构体成员内存中重新排列可省内存,但目前不是
cgo
o-> c文件
#include <bzlib.h>
int bz2compress(bz_stream *s, int action, char *in, unsigned *inlen, char *out, unsigned *outlen) {...}
o-> go文件
/*
#cgo CFLAGS: -I/usr/include
#cgo LDFLAGS: -L/usr/lib -lbz2
#include <bzlib.h>
int bz2compress(bz_stream *s, int action, char *in, unsigned *inlen, char *out, unsigned *outlen);
*/
import "C"
import (
"io"
"unsafe"
)
type writer struct {
w io.Writer
stream *C.bz_stream
outbuf [64*1024]byte
}
func NewWriter(out io.Writer) io.WriteCloser{
const (
blockSize = 9
verbosity = 0
workFactor = 30
)
w := &writer{w: out, stream: C.bz2alloc()}
C.BZ2_bzCompressInit(w.stream, blockSize, verbosity, workFactor)
return w
}
func (w *writer) Write(data []byte) (int, erro) {
if w.stream == nil {
panic("closed")
}
var total int
for len(data) > 0 {
inlen, outlen := C.uint(len(data)), C.uint(cap(w.outbuf))
C.bz2compress(w.stream, C.BZ_RUN, (*C.char)(unsafe.Pointer(&data[0])), &inlen, (*C.char)(unsafe.Pointer(&w.outbuf)), &outlen)
total += int(inlen)
data = data[inlen:]
if _, err := w.w.Write(w.outbuf[:outlen]); err != nil {
return total, err
}
}
return total, nil
}
注释
#cgo 指令指定C工具链选项
import "c"
编译时促使go build用cgo预处理其上注释
产生临时包包含c函数对应声明
包含类型,函数,预处理宏对象
这里用了C.bz_stream和C.BZ2_bzCompressInit
go也可编译成静态库链接进C, 或编译成动态库通过C加载和共享
内部包
#
# golang.org/pkg 找到索引
errors
New()
testing
T
Error()
Errorf()
Fatal()
Fatalf()
B
syscall # 执行其它语言
syscall/js
js/wasm # 1.11, webAssembly
go/doc
go/token
runtime
Stack() # 调用栈
Gosched() # 让出执行权
Goexit() # 终止当前goroutine, 会执行defer
LockOSThread() # 绑定协程到当前线程
UnlockOSThread()
GOMAXPROCS() # 并发线程数
NumGoroutine() # 限制goroutine数
runtime/debug
os
Stdin # 输入流
Args # 运行参数
FileInfo
Open() # 打开文件
File
Read()
Write()
Close()
Exit(1) # 1异常退出
RemoveAll()
Stat() # 文件信息
os/exec # 子进程
io
EOF # 文件结束标志, 是一个error
Copy()
WriteString()
io/ioutil
Discard # 丢弃
ReadFile() # 读整个文件到内存
ReadAll()
WriteFile()
ReadDir()
bufio # 带缓冲io
NewScanner()
Scanner # 以行或单词断开
Scan() # 有内容返回true
Text()
NewReader()
ReadRune()
path # 文件路径
Base() # 获得最后文件名
path/filepath # 根据平台处理文件路径
net
Conn
net/http
poolServer(epoll/kqueue/iocp)
# 支持多核大量并发连接fd
Get()
Header
Get()
Body
Close()
HandleFunc()
ResponseWriter
Request
RemoteAddr # 客户ip:端口
Host
Method
Proto # 网络协议
Header
Form # 先ParseForm()
URL
Path
ParseForm()
ListenAndServe()
net/http/httputil
net/url
QueryEscape() # url转义
context # 线程安全, 树形结构
Cancel()
Deadline(Timeout)
Value()
TODO()
o-> ctx.Done()
func f(ctx context.Context) (error) {
errc := make(chan error, 1)
go func() {
defer close(errc)
time.Sleep(2 * time.Second)
errc <- nil
}()
select {
case <-ctx.Done():
<-errc
return ctx.Err()
case err := <-errc:
return err
}
}
o-> WithTimeout
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) # 调cancel提前结束
defer cancel()
return f(ctx)
flag
Args # 非标识参数
Parse() # 出错调os.Exit(2)
o-> 输入'-s / a bc', 输出'a/bc'
sep := flag.Strings("s", " ", "desc s")
flag.Parse()
println(strings.Join(flag.Args(), *sep))
log
SetPrefix()
SetFlags() # 格式标记
Fatal() # 加日期时间前缀
Fatalf()
time
Time
Format()
Now()
Since()
Seconds()
After()
AfterFunc()
NewTicker()
ticker := time.NewTicker(1 * time.Second)
<- ticker.C
ticker.Stop()
Tick()
tick := time.Tick(1 * time.Second) # 无法中止, 用于全局,否则内部goroutine泄漏
for {
<-tick
}
fmt # string类型会调对象的String()方法
Stringer # 接口,有String()方法可匹配
Printf() # 可以用转义序列(\t等)表示不可见字符
%d # 十进制整数
%x, %o, %b # 十六进制、八进制、二进制整数
%X
% x # 十六进制输出,两数一空格
%f, %g, %e # 浮点数6位小数、15位小数并自动精度与表示方式、6位小数e表示
"%8.3f" # 输出8字符宽度,保留3位小数
%t # 布尔
%c # unicode字符
%s # 字符串
%*s # 缩进后面指定数字个空格
%q # 带引号字符串("abc"),或字符('c')
%v # 内置格式的任何值
%#v # 包含结构体成员名字
%T # 类型
%[1]c # 重复利用第一个参数
Printf("%d %[1]c %[1]q", 'a')
%% # %本身
特殊数字
var z float64
fmt.Println(z,-z,1/z,-1/z,z/z) # "0 -0 +Inf -Inf NaN"
Fprintf()
Scanf() # 格式化输入
Errorf() # 产生一个error
strconv # 类型转换
ParseFloat()
ParseInt()
ParseUint()
Itoa() # 整数转字符串
FormatInt(int64(1a), 2)
FormatUint()
unicode # 单符号
ToUpper()
ToLower()
IsDigit()
IsLetter()
unicode/utf8 # 逐个处理
RuneCountInString() # 字符数
DecodeRuneInString() # 解码, string类型默认调用
bytes # 操作byte数组
Buffer
WriteByte()
WriteRune()
WriteString()
String()
Index()
Contains()
Count()
Fields()
HasPrefix()
Join()
Equal()
strings # 处理UTF-8或位序列
Index()
Split()
HasPrefix()
HasSuffix()
Contains()
Count()
Fields()
Join()
regexp
MustCompile() # 检查
Compile() # 编译表达式
text/template
Must() # 检查,有错panic
o->
{{.Name}} # .代表当前值
{{range .Items}}
{{.Title | printf "%.64s"}}
{{.CreateAt | daysAgo}}
{{end}}
template.New("report").
Funcs(template.FuncMap{"daysAgo": daysAgo}).
Parse(templ)
text/tabwriter # 生成表格
Flush()
html/template # 对html, js, css, url中原字符转义, 避免对输出页面注入控制
Html # 字符串转成该类型,受信任,不用转义
Parse() # 解析html
encoding/json # unicode
Marshal() # 转成json, 不可导出不可见
MarshalIndent() # 格式化转成json
Unmarshal()
NewDecoder() # 流式解码
Decode()
encoding/xml
encoding/gob
encoding/asn1
compress/gzip # DEFLATE算法
NewWriter()
NewReader()
compress/bzip2 # Burrows-Wheeler变换, 压缩高,慢
sort
IntSlice
sort.Sort(sort.IntSlice(ints))
Sort()
Reverse()
sort.Sort(sort.Reverse(values))
IsSorted()
Strings()
Ints()
IntsAreSorted()
o->
type StringSlice []string
func (p StringSlice) Len()int {return len(p)}
func (p StringSlice) Less(i, j int)bool {return p[i] < p[j]}
func (p StringSlice) Swap(i, j int) {p[i], p[j] = p[j], p[i]}
sort.Sort(StringSlice(names))
math
Sin()
NaN() # 返回NaN, NaN值比较总false(除!=), NaN用作信号量
math/rand
Seed(time.Now().UTC().UnixNano())
Float64()
math/cmplx # 复数运算
Sqrt(-1) # 0 + 1i
math/bits
image
Rect()
NewPaletted()
SetColorIndex()
Decode()
Encode()
image/color
Color
White
Black
image/gif
GIF
image/jpeg # 空导入注册解码器
image/png # 空导入注册解码器
sync
Mutex
Lock()
Unlock()
RWMutex
Lock()
Unlock()
RLock()
RUnlock()
Once # 单例资源初始化,解决了多线程下读检查,防重写的问题
Do()
WaitGroup
Cond
Wait() # 计数加1, 进入阻塞
Signal() # 解除一个阻塞,计数减1
Broadcast() # 解除所有阻塞
Map
Pool
reflect # 非导出字段反射可见, 不可更新
Type # 类型
String() # 类型描述, fmt.Printf()中的"%T" 内部调用
Field() # 结构体成员, 返回StructField
Name
Method()
Value # 值
String() # 值描述,如"<int Value>"
Type()
Interface() # 返回接口具体值
x := v.Interface()
i := x.(int)
Kind() # 得到类型,Bool, String, 各种数字, Array, Struct, Chan, Func, Ptr, Slice, Map, Interface, Invalid(零值), Func
Index() # 数组
NumField() # 结构体成员数
FieldByName()
MapKeys() # map
MapIndex() # map
IsValid()
IsNil() # 指针
Elem() # 指针指向元素
CanAddr() # 是否可寻址,如指针元素取Elem()的值,数组元素
Addr() # 取地址
v.Addr().Interface().(*int)
CanSet() # 检查CanAddr()和是否非导出字段
Set() # 要求可寻址, 类型一致。可Set()interface{}类型
SetInt(), SetUint(), SetString(), SetFloat() # 相对Set()有容错性,不可SetXx()interface{}类型
SetMapIndex()
NumMethod() # 方法数
Method() # 取方法
Name
Call() # 执行Func类型Value
StructField
Tag
StructTag
Get() # 字段标签中key的值
Method
TypeOf()
ValueOf()
Zero() # 零值
Append()
MakeMap()
New() # 类型新对象地址
DeepEqual() # 深度比较,基本类型用==, 组合类型逐层比较。
判断武断,不认为值为nil的map和值不为nil的空map相等。slice同理
var c, d map[string]int = nil, make(map[string]int)
var a, b []string = nil, []string{}
unsafe # 由编译器实现,暴露了内存布局。
Pointer # 任何地址,可比较,可比较nil。无类型向内存写任意值。
可转成uintptr对地址计算
问题
移动垃圾回收器(目前未用)在移变量时地址改变使地址出错。
goroutine增长栈时旧栈地址重新分配
解决
Pointer转uintptr再转回来在一条语句中实现
应用
深度比较时,防止循环引用,每次比较存两个抽象的指针(即Pointer)和类型(y和y[0]地址一样)
var f float64
i := *(*uint64)unsafe.Pointer(&f)
Sizeof() # 表达式占字节长度, 不计算表达式,由编译器推断
Alignof # 报告类型对齐方式
Offsetof() # 成员相对起始偏移量, 计算空位
外部包
#
# godoc.org 搜索
goimports # 格式化imports顺序
测试
#
规则
文件名以_test.go结尾
汇报PASS或FAIL, 平均执行时间
忽略main函数, 当作库测试
main特权函数 log.Fatal()和os.Exit()会阻止跟踪过程
包测试循环依赖时,建立外部测试包
导出内部成员用于测试的后门成员声明,放在export_test.go内
机制
扫描*_test.go
生成临时main包来调用,再编译、运行、汇报, 最后清空临时文件
Test函数 # t用于汇报结果和日志
func TestF(t *testing.T) {}
benchmark函数 # 基准测试,性能
b增加了成员N指定执行次数, 增加了性能检测方法
开始指定小N, 再推断足够大的N检测稳定运行时间
基准测试时初始化代码放循环外面,它的执行时间不加到每次迭代时间中。普通Test做不到
用go test -bench=.运行
报告中 f-8 1000000 1035 ns/op 分别代表GOMAXPROCS=8, 执行100000次,平均每次1035ns
o-> 基本使用
func BenchmarkF(b *testing.B) {
for i := 0; i < b.N; i++{
f()
}
}
o-> 相对比较, 如数量级、找最佳缓冲区大小、选算法策略
func benchmark(b *testing.B, size int){}
func Benchmark10(b *testing.B) {benchmark(b, 10)}
func Benchmark100(b *testing.B) {benchmark(b, 100)}
Example函数 # 示例,无参无结果。
用处
可举例子作为文档
结尾注释 // output: 验证终端输出
实验代码
func ExampleF() {
fmt.Print("a")
// output: aa
}
并发编程
#
同步
func
channel # 和调度器深度关联,控制goroutine的阻塞和唤醒
缓冲区
作用
异步
发送接收解耦
让数据可并行处理(计数信号量)
消除goroutine间速率差异(速率大致相同, 某刻休息)
上下游速率差异大时无作用
阻塞时同步
c := make(chan struct{})
c1 := make(chan struct{}, 1)
c ← struct{}{} # 阻塞
← c # 阻塞
c1 ← struct{}{} # 不阻塞
c1 ← struct{}{} # 阻塞
← c1 # 不阻塞
← c1 # 阻塞
方向
var c chan struct{} # in和out
var cin <-chan struct{} # in, 关闭时panic
v := <-cin
var cout chan<- struct{} # out
cout <- v
cin = c
cout = c
c = cin # 编译错误
c = cout # 编译错误
nil # 永远阻塞, 用于开启禁用情况
var c chan struct{}
c <- struct{}{} # 阻塞
关闭 # 关闭不是必须的,不影响回收。只是用来通知和广播
c := make(chan struct{})
close(c) # 再关闭panic
c ← struct{}{} # panic
o, ok := ← c # o得到零值, ok是false
for range
c := make(chan struct{})
...
for x := range c {} # close(c)时break
select
sync包
sync/atomic包
o-> 并发三个业务, 一起结束
cond := sync.NewCond(new(sync.Mutex))
wg := sync.WaitGroup{}
wg.Add(3)
wg1 := sync.WaitGroup{}
wg1.Add(3)
for i := 0; i < 3; i++ {
go func(i int) {
defer wg1.Done()
cond.L.Lock()
fmt.Println("wait", i) # 业务预处理
wg.Done()
cond.Wait() # 阻塞
fmt.Println("done", i) # 业务后续处理(要求所有业务预处理过)
cond.L.Unlock()
}(i)
}
wg.Wait() # 业务预处理完成
cond.L.Lock()
cond.Broadcast() # 处理业务后续
cond.L.Unlock()
wg1.Wait() # goroutine完成
异步
语句
语句是串行一致的(sequentially consistent)
串行一致基础上,语句会重排, 重排中可能穿插执行其它goroutine语句
t := map[string]int{
"a": 1
"b": 2
}
重排为
t := make(map[string]int)
t["a"]=1
t["b"]=2
goroutine
语句
go f()
泄漏
阻塞不能自动结束 # 如操作channel时
main中最后调panic(), 从崩溃转储信息判断资源释放情况
死锁(deadlock) # 指没有可调度的goroutine
所有goroutine阻塞或没有goroutine
运行main的是主goroutine, main返回所有goroutine暴力终结
无id(标识)
不能中断
无返回值
runtime
context
time
并发模式 # 避免goroutine泄漏,保证通信顺序
done/quit
o-> done控制goroutine退出。 # 更快的响应要写更多的逻辑入侵,找到响应慢点写done逻辑
func f(done <-chan struct{}) {
select {
case <-done:
for range ch{ # 耗尽通道, 其它goroutine不会卡在ch<-上而退出
}
return
}
}
func cancelled()bool{
select {
case <-done:
return true
default:
return false
}
}
func f2(){ # 轮循函数中入口检查, 避免创建新goroutine
if cancelled() {
return
}
}
done := make(chan struct{})
defer close(done)
f(done)
channels of channels
o-> 循环处理请求
func handle(reqs chan chan interface{}) {
for req := range reqs {
req <- 0
}
}
func server(req chan interface{}) {
reqs := make(chan chan interface{})
defer close(reqs)
go handle(reqs)
reqs <- req
}
func client() interface{} {
req := make(chan interface{})
defer close(req)
go server(req)
return <-req
}
fmt.Println(client())
o-> 循环异常退出
type S struct {
closing chan chan error
}
func (s *S) close() error {
errc := make(chan error)
s.closing <- errc
return <-errc
}
func (s *S) loop() {
for {
select {
case errc := <-s.closing:
errc <- nil
return
}
}
}
pipeline(fan-in, fan-out) # 传入传出channel来处理
o->
func gen(done <-chan struct{}, nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
select {
case out <- n:
case <-done:
return
}
}
}()
return out
}
func sq(done <-chan struct{}, in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
select {
case out <- n * n:
case <-done:
return
}
}
}()
return out
}
func merge(done <-chan struct{}, cs ...<-chan int) <-chan int {
# wg等cs数目个协程合并数据到out后,关闭out
var wg sync.WaitGroup
out := make(chan int)
output := func(c <-chan int) {
for n := range c {
select {
case out <- n:
case <-done:
}
}
wg.Done()
}
wg.Add(len(cs))
for _, c := range cs {
go output(c)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
func main() {
done := make(chan struct{})
defer close(done)
for n := range sq(done, sq(done, gen(done, 2, 3))) {
# gen产生维护数字chan, sq产生维护平方chan。三个chan
# 三个goroutine done()时return, chan return时close()
fmt.Println(n)
}
// 扇出
in := gen(done, 2, 3)
c1 := sq(done, in)
c2 := sq(done, in)
// 扇进
for n := range merge(done, c1, c2) {
fmt.Println(n)
}
}
timeout
select {
case <-ch:
...
case <-time.After(time.Second)
return
}
控制并发数
并发写缓冲区channel
for循环产生并发数goroutine
常用
中断
# os.Exit()程序返回错误码
done := make(chan struct{})
go func() {
defer close(done)
c := make(chan os.Signal, 1)
defer close(c)
signal.Notify(c, os.Interrupt, os.Kill)
defer signal.Stop(c)
<-c
}()
并发压测
func concurrent(done chan struct{}, fn func(), num int, ccu int, qps int) { # num总数,ccu并行数,qps并发数
interval := time.Duration(1e9/qps) * time.Nanosecond
don := make(chan struct{}, 2)
go func() {
<-done
for i := 0; i < ccu; i++ {
don <- struct{}{}
}
}()
//
tasks := make(chan struct{})
go func() {
var wg sync.WaitGroup
wg.Add(num)
for i := 0; i < num; i++ {
tasks <- struct{}{}
wg.Done()
time.Sleep(interval)
}
wg.Wait()
close(tasks)
}()
//
var wg sync.WaitGroup
wg.Add(ccu)
for i := 0; i < ccu; i++ {
go func() {
defer wg.Done()
for range tasks {
select {
case <-don:
return
default:
fn()
}
}
}()
}
wg.Wait()
}
m := sync.Mutex{}
count := 0
do := func(){
m.Lock()
count++
m.Unlock()
}
concurrent(done, do, 999, 100, 1e3)