0%

Context(即上下文)是常用的并发控制技术,可以控制一组多级的goroutine。
Context 仅仅是一个接口定义,跟据实现的不同,可以衍生出不同的context类型。

上下文有下面四种类型

1、WithCancel

1
2
3
4
5
6
func main() {
ctx, cancel := context.WithCancel(context.Background())
go HandelRequest(ctx)
time.Sleep(1 * time.Second)
cancel()
}

2、WithDeadline

1
2
3
4
5
6
7
8
9
10
func main() {
now := time.Now()
//3秒后自动过期
//WithDeadline其实跟WithTimeout一样,只是传入的参数不一样,
//一个是具体时间,一个是相对时间,通常来说使用WithTimeout就足够
ctx, cancel := context.WithDeadline(context.Background(), now.Add(3*time.Second))
go HandelRequest(ctx)
time.Sleep(3 * time.Second)
cancel()
}

3、WithTimeout

1
2
3
4
5
6
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
go HandelRequest(ctx)
time.Sleep(10 * time.Second)
cancel()
}

4、WithValue

作用是可以传递参数给到其他goroutine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
ctx := context.WithValue(context.Background(), "id", "1")
go HandelRequest(ctx)
time.Sleep(10 * time.Second)
}

func HandelRequest(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("HandelRequest Done.")
return
default:
fmt.Println("HandelRequest running, parameter: ", ctx.Value("parameter"))
time.Sleep(2 * time.Second)
}
}
}

安装工具

最简单的方式

1
$ go get -u golang.org/x/tool/...

替代方式

1
$ GO111MODULE=on go get golang.org/x/tool/cmd/stress

环境变量

1
2
3
4
$ go env
$ go env GOPATH GOO GOARCH
$ go env -w GOPROXY=https://goproxy.cn,direct
$ go help environment

开发

运行代码

1
2
3
4
5
6
7
8
9
$ go run .                      # 运行当前目录的包 
$ go run ./cmd/foo # 运行 ./cmd/foo 目录下的包 获取依赖
$ go get github.com/foo/bar@v1.2.3
$ go get github.com/foo/bar@8e1b8d3
$ go list -m all # 显示所有依赖
$ go mod why -m golang.org/x/y # 为什依赖它?
$ go clean -modcache # 清除模块缓存 格式化代码
$ gofmt -d -w -r 'foo -> Foo' . # 替换 foo 为 Foo
$ gofmt -d -w -r 'strings.Replace(a, b, -1) -> strings.ReplaceAll(a, b)' .

查看 Go 文档

1
2
3
4
5
6
$ go doc strings                # 查看 strings 包的简化版文档 
$ go doc -all strings # 查看 strings 包的全部文档
$ go doc strings.Replace # 查看 strings.Replace 函数的文档
$ go doc sql.DB # 查看 database/sql.DB 类型的文档
$ go doc sql.DB.Query # 查看 database/sql.DB.Query 方法的文档
$ go doc -src strings.Replace # 查看 strings.Replace 函数的源码

Testing

运行测试

1
2
3
4
5
6
7
8
9
10
11
$ go test .                      # 运行当前目录的所有测试 
$ go test ./... # 运行当前目录及其子目录的所有测试
$ go test ./foo/bar # 运行 ./foo/bar 目录的所有测试
$ go test -race ./... # 启用 race 检测的测试
$ go test -count=1 ./... # 运行测试时绕过测试缓存
$ go clean -testcache # 删除所有缓存的测试结果
$ go test -v -run=^TestFooBar$ . # 运行名称为 TestFooBar 的测试
$ go test -v -run=^TestFoo . # 运行名称以 TestFoo 开头的测试
$ go test -v -run=^TestFooBar$/^Baz$ . # 仅运行 FooBar 测试下的 Baz 子测试
$ go test -short ./... # 跳过长时间运行的测试
$ go test -failfast ./... # 失败后请勿再进行进一步的测试

Profiling Test Coverage

1
2
3
4
5
6
7
$ go test -cover ./... 
$ go test -coverprofile=/tmp/profile.out ./... # coverage profile for browser
$ go tool cover -html=/tmp/profile.out
$ go test -covermode=count -coverprofile=/tmp/profile.out ./... # coverage with frequency shown
$ go tool cover -html=/tmp/profile.out
$ go test -coverprofile=/tmp/profile.out ./... # coverage in CLI without any browser
$ go tool cover -func=/tmp/profile.out

压力测试

1
2
3
$ go test -run=^TestFooBar$ -count=500 . 
$ go test -c -o=/tmp/foo.test . # 使用压力工具 golang.org/x/tools/cmd/stress
$ stress -p=4 /tmp/foo.test -test.run=^TestFooBar$

Testing all dependencies

1
$ go test all

提交前检查

格式化代码

1
2
3
$ gofmt -w -s -d foo.go   # 格式化 foo.go 文件 
$ gofmt -w -s -d . # 递归格式化当前目录和子目录所有文件
$ go fmt ./... # 另外一种格式化工具,等价 gofmt -l -w ./...

通过 vet 进行静态分析

1
2
3
4
5
$ go vet foo.go           # 检查 foo.go 文件 
$ go vet . # 检查当前目录下所有文件
$ go vet ./... # 检查当前目录及子目录下所有文件
$ go vet ./foo/bar # 检查 ./foo/bar 目录下所有文件
$ go vet -composites=false ./... # 禁用一些分析器

实验性的分析器

1
2
3
4
$ cd /tmp 
$ GO111MODULE=on go get golang.org/x/tools/go/analysis/passes/nilness/cmd/nilness
$ GO111MODULE=on go get golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
$ go vet -vettool=$(which nilness) ./...

测试时禁用 vet 工具

1
$ go test -vet=off ./...

整理代码(LintingCode)

1
2
3
4
5
6
$ cd /tmp          # 安装 linter 
$ GO111MODULE=on go get golang.org/x/lint/golint
$ golint foo.go # Lint the foo.go file
$ golint . # Lint all files in the current directory
$ golint ./... # Lint all files in the current directory and sub-directories
$ golint ./foo/bar # Lint all files in the ./foo/bar directory

整理和验证依赖

1
2
$ go mod tidy      # 移除所有未使用过的依赖 
$ go mod verify # 检查依赖的 hash

编译和部署

编译可执行文件

1
2
$ go build -o=/tmp/foo .         # 编译当前目录的包 
$ go build -o=/tmp/foo ./cmd/foo # 编译 ./cmd/foo 目录的包

编译缓存

1
2
3
$ go env GOCACHE                 # 检查你的编译缓存存放目录 
$ go build -a -o=/tmp/foo . # 强制重编译所有包
$ go clean -cache # 清除缓存

编译过程

1
2
$ go list -deps . | sort -u      # 列出用于编译可执行文件的所有包 
$ go build -a -x -o=/tmp/foo . # 重建所有内容并显示运行的命令

交叉编译

1
2
3
$ GOOS=linux GOARCH=amd64 go build -o=/tmp/linux_amd64/foo . 
$ GOOS=windows GOARCH=amd64 go build -o=/tmp/windows_amd64/foo.exe .
$ go tool dist list # 列出所有支持的操作系统和CPU架构

使用编译器和链接器 flags

1
2
3
4
5
6
7
8
$ go tool compile -help                       # 查看编译器可用的 flag 
$ go build -gcflags="-m -m" -o=/tmp/foo . # 打印有关优化决策的信息
$ go build -gcflags="all=-m" -o=/tmp/foo . # 打印包括依赖的优化决策信息
$ go build -gcflags="all=-N -l" -o=/tmp/foo . # 禁用优化和内联
$ go tool link -help # 查看链接器可用的 flag
$ go build -ldflags="-X main.version=1.2.3" -o=/tmp/foo . # 增加版本信息
$ go build -ldflags="-s -w" -o=/tmp/foo . # 从二进制文件中删除调试信息
$ CGO_ENABLE=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' . # 使二进制文件尽可能静态

问题诊断和优化

运行和比较基准测试

1
2
3
4
5
6
7
8
$ go test -bench=. ./...                                  # 运行所有测试和基准测试 
$ go test -run=^$ -bench=. ./... # 只运行所有的基准测试
$ go test -run=^$ -bench=^BenchmarkFoo$ ./... # 只运行 BenchmarkFoo 基准测试
$ go test -bench=. -benchmem ./... # 强制将内存分配统计信息包含在输出中
$ go test -bench=. -benchtime=5s ./... # 运行每个基准测试至少5秒钟
$ go test -bench=. -benchtime=500x ./... # 保证每个基准测试精确进行500次迭代
$ go test -bench=. -count=3 ./... # 重复每个基准测试3次
$ go test -bench=. -cpu=1,4,8 ./.... # 将GOMAXPROCS设置为1、4和8运行基准测试

比较基准测试的变化

1
2
3
4
5
$ cd /tmp                                                 # 安装 benchcmp 工具 
$ GO111MODULE=on go get golang.org/x/tools/cmd/benchcmp
$ go test -run=^$ -bench=. -benchmem ./... > /tmp/old.txt # 进行一些修改、优化
$ go test -run=^$ -bench=. -benchmem ./... > /tmp/new.txt
$ benchcmp /tmp/old.txt /tmp/new.txt

Profiling and Tracing

运行和比较基准测试

1
2
3
4
5
6
7
$ go test -run=^$ -bench=^BenchmarkFoo$ -cpuprofile=/tmp/cpuprofile.out . 
$ go test -run=^$ -bench=^BenchmarkFoo$ -memprofile=/tmp/memprofile.out .
$ go test -run=^$ -bench=^BenchmarkFoo$ -blockprofile=/tmp/blockprofile.out .
$ go test -run=^$ -bench=^BenchmarkFoo$ -mutexprofile=/tmp/mutexprofile.out .
$ go test -run=^$ -bench=^BenchmarkFoo$ -o=/tmp/foo.test -cpuprofile=/tmp/cpuprofile.out .
$ go tool pprof -http=:5000 /tmp/cpuprofile.out # 在浏览器中审查
$ go tool pprof --nodefraction=0.1 -http=:5000 /tmp/cpuprofile.out # 忽略小于 10% 的节点

Tracing 生成

1
2
$ go test -run=^$ -bench=^BenchmarkFoo$ -trace=/tmp/trace.out . 
$ go tool trace /tmp/trace.out # 目前只在 Chrome/Chromium 可用

竞态条件检查

1
2
$ go build -race -o=/tmp/foo .         # 别用于生产环境 
$ GORACE="log_path=/tmp/race" /tmp/foo # 输出到文件而不是标准错误

依赖管理(Module)

项目依赖更新

1
2
$ go list -m -u github.com/alecthomas/chroma  # 检查该库是否有新版本 
$ go list -m -u all # 更新项目所有依赖

依赖升级或降级

1
2
3
$ go get github.com/foo/bar@latest            # 最新版本 
$ go get github.com/foo/bar@v1.2.3 # 特定版本 v1.2.3
$ go get github.com/foo/bar@7e0369f # 到特定提交

运行所有包的全部测试检验不兼容性

1
$ go mod tidy $ go test all

使用依赖的本地版本

1
2
$ go mod edit -replace=github.com/alexedwards/argon2id=/home/alex/code/argon2id # 创建 replace 规则 
$ go mod edit -dropreplace=github.com/alexedwards/argon2id # 删除 replace 规则

其他工具

升级代码到 Go 新版本

1
$ go fix ./... 

报告 Bug

1
$ go bug # 会打开浏览器,定位 Go 代码仓库的 issue 页面

查询出没有该字段的文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET {index}/_count
{
"query": {
"bool": {
"must_not": [
{
"exists": {
"field": "字段名称"
}
}
]
}
}
}

查询字段值为空字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET {index}/_count
{
"query": {
"bool": {
"must_not": [
{
"wildcard": {
"字段名称": {
"value": "*"
}
}
}
]
}
}
}

查询字段值不为空字符串的文档

1
2
3
4
5
6
7
8
9
10
GET {index}/_count
{
"query": {
"wildcard": {
"字段名称": {
"value": "*"
}
}
}
}

defer关键词规则

  • 规则一:延迟函数的参数在defer语句出现时就已经确定下来了(因为会有拷贝)
  • 规则二:延迟函数执行按后进先出顺序执行,即先出现的defer最后执行(filo,栈操作)
  • 规则三:延迟函数可能操作主函数的”具名”返回值
    • 对于指针类型参数,规则仍然适用,只不过延迟函数的参数是一个地址值,
    • 这种情况下,defer后面的语句对变量的修改可能会影响延迟函数。

示例

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
28
29
30
31
32
33
34
35
36
37
38
39
40
func variableDemo1() {
for i := 1; i <= 5; i++ {
//i会进行一次拷贝到t,所以能正常打印数据
defer func(t int) {
fmt.Printf("t = %d \n", t)
}(i)

//这里只会输出6,因为i在defer已经执行完循环,i指向都是最终值
defer func() {
fmt.Printf("t2 = %d \n", i)
}()
}
}

//返回前执行defer,此时t=2,++后返回3
func returnVariableDemo1() (t int) {
defer func() {
t++
}()
return 2
}

//返回前执行defer,但是无法赋值结果,无返回t变量,返回的是2
func returnVariableDemo2() int {
t := 2
defer func() {
t++
}()
return t
}

func variableDemo2() (t int) {
//这里传入的t是初始值只0,defer开始时就拷贝0到i,第二个输出t是ret前赋值为2,所以是2
defer func(i int) {
fmt.Println("variableDemo2 i = ", i) // 0
fmt.Println("variableDemo2 t = ", t) // 2
}(t)
t = 1
return 2
}

在java中所有的map都实现了Map接口,因此所有的Map(如HashMap, TreeMap, LinkedHashMap, Hashtable等)都可以用以下的方式去遍历。

方法一:在for循环中使用entries实现Map的遍历:

1
2
3
4
5
6
7
8
9
10
11
/**
* 最常见也是大多数情况下用的最多的,一般在键值对都需要使用
*/
Map <String,String>map = new HashMap<String,String>();
map.put("熊大", "棕色");
map.put("熊二", "黄色");
for(Map.Entry<String, String> entry : map.entrySet()){
String mapKey = entry.getKey();
String mapValue = entry.getValue();
System.out.println(mapKey+":"+mapValue);
}

方法二:在for循环中遍历key或者values,一般适用于只需要map中的key或者value时使用,在性能上比使用entrySet较好;

1
2
3
4
5
6
7
8
9
10
11
Map <String,String>map = new HashMap<String,String>();
map.put("熊大", "棕色");
map.put("熊二", "黄色");
//key
for(String key : map.keySet()){
System.out.println(key);
}
//value
for(String value : map.values()){
System.out.println(value);
}

方法三:通过Iterator遍历;

1
2
3
4
5
6
7
Iterator<Entry<String, String>> entries = map.entrySet().iterator();
while(entries.hasNext()){
Entry<String, String> entry = entries.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+":"+value);
}

方法四:通过键找值遍历,这种方式的效率比较低,因为本身从键取值是耗时的操作;

1
2
3
4
for(String key : map.keySet()){
String value = map.get(key);
System.out.println(key+":"+value);
}