Golang 的一些笔记

发布于 2022-09-22 23:53:32 字数 68442 浏览 4 评论 0

Golang 多版本安装

method1 官方

$ go get -u golang.org/dl/go1.12.3
go: finding golang.org/dl latest
go: downloading golang.org/dl v0.0.0-20190408222801-b337094d5ff3
go: extracting golang.org/dl v0.0.0-20190408222801-b337094d5ff3
$ go1.12.3 download
Downloaded 100.0% (127615731 / 127615731 bytes)
Unpacking /Users/bingoobjca/sdk/go1.12.3/go1.12.3.darwin-amd64.tar.gz ...
Success. You may now run 'go1.12.3'
$ go1.12.3 version
go version go1.12.3 darwin/amd64
$ go version
go version go1.12.1 darwin/amd64

method2 gvm

install bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)

influxdb 推荐使用 gvm 来构建:

$ gvm install go1.15
$ gvm use go1.15 --default

相关书籍

  1. 《Golang修养之路》本书针对Golang专题性热门技术深入理解,修养在Golang领域深入话题,脱胎换骨。
  2. Go语言圣经(中文版)
  3. Modern Go Programming DSL for Query: use SQL to query in memory collection, and any databases
  4. Essential Go
  5. Go语言高级编程(Advanced Go Programming)
  6. Go语言101 一本着墨于Go语法和语义的编程指导书
  7. 深入Go并发编程研讨课
  8. go教程电子书
  9. 深入解析Go
  10. Go 语言中文开源图书、资料或文档
  11. Go专家编程
  12. Go语言爱好者周刊
  13. Uber Go语言开发规范
  14. gopherchina/conference Golang conference PPT
  15. Effective Go, Go Code Review Comments
  16. 煎鱼的迷之博客 跟煎鱼学go
  17. Go 语言设计与实现
  18. Christmas 2020 Go class slides

无缓冲读

try online

package main

import "fmt"

func main() {
	// create unbuffered channel of int values with capacity of 1
	ch := make(chan int)
	
	select {
	    case ch <- 3: 
	    // uncomment the following line to get this program work
	    // default:
	}
	
	fmt.Printf("ok\n")
}
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
	/tmp/056148531/main.go:10 +0x60
exit status 2

一些库

  1. Go 语言 Excel 类库 Excelize
  2. Kratos 是 bilibili 开源的一套 Go 微服务框架,包含大量微服务相关框架及工具。

一些工具链

dogsled a Go static analysis tool to find assignments/declarations with too many blank identifiers (e.g. x, _, _, _, := f()). Its name was inspired from this reddit post.

包管理

经常看到go.mod文件中,有一些indirect的包,是怎么被依赖进来的,可以用命令 go mod why yourAwesomePackage 来查看。比如:

# bingoo @ 192 in ~/GitHub/loglineparser on git:master x [12:40:02]
$ go mod why gopkg.in/yaml.v2
# gopkg.in/yaml.v2
github.com/bingoohuang/loglineparser
github.com/araddon/dateparse
github.com/araddon/dateparse.test
github.com/simplereach/timeutils
gopkg.in/mgo.v2/bson
gopkg.in/mgo.v2/bson.test
gopkg.in/yaml.v2

StackOverflow 上的一篇帖子

As of the final 1.11 release, the go module cache (used for storing downloaded modules and source code), is in the $GOPATH/pkg/mod location (see the docs here). For clarification, the go build cache (used for storing recent compilation results) is in a different location.

This article, indicated that it's in the $GOPATH/src/mod, but in the timespan of the recent ~40 days, the golang team must have changed that target location. This message thread has some discussion on why the downloaded items ended up in $GOPATH/pkg.

You can also use the go mod download -json command to see the downloaded modules/source metadata and their location on your local disk. Example output below:

$ go mod download -json
go: finding github.com/aws/aws-sdk-go v1.14.5
go: finding github.com/aws/aws-lambda-go v1.2.0

{
    "Path": "github.com/aws/aws-lambda-go",
    "Version": "v1.2.0",
    "Info": "/go/pkg/mod/cache/download/github.com/aws/aws-lambda-go/@v/v1.2.0.info",
    "GoMod": "/go/pkg/mod/cache/download/github.com/aws/aws-lambda-go/@v/v1.2.0.mod",
    "Zip": "/go/pkg/mod/cache/download/github.com/aws/aws-lambda-go/@v/v1.2.0.zip",
    "Dir": "/go/pkg/mod/github.com/aws/aws-lambda-go@v1.2.0",
    "Sum": "h1:2f0pbAKMNNhvOkjI9BCrwoeIiduSTlYpD0iKEN1neuQ=",
    "GoModSum": "h1:zUsUQhAUjYzR8AuduJPCfhBuKWUaDbQiPOG+ouzmE1A="
}
{
    "Path": "github.com/aws/aws-sdk-go",
    "Version": "v1.14.5",
    "Info": "/go/pkg/mod/cache/download/github.com/aws/aws-sdk-go/@v/v1.14.5.info",
    "GoMod": "/go/pkg/mod/cache/download/github.com/aws/aws-sdk-go/@v/v1.14.5.mod",
    "Zip": "/go/pkg/mod/cache/download/github.com/aws/aws-sdk-go/@v/v1.14.5.zip",
    "Dir": "/go/pkg/mod/github.com/aws/aws-sdk-go@v1.14.5",
    "Sum": "h1:+l1m6QH6LypE2kL0p/G0Oh7ceCv+IVQ1h5UEBt2xjjU=",
    "GoModSum": "h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k="
}

JSON

JSON 数据解析,直接给字段赋予 json 字符串,避免转义

run

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	s := `[` + `{"name":"bingoo"},{"name":"dingoo"}` + `]`
	var arr []interface{}
	if err := json.Unmarshal([]byte(s), &arr); err != nil {
		panic(err)
	}

	m := map[string]interface{}{"key1": arr, "key2": s, "key3": json.RawMessage([]byte(s))}
	jso, err := json.Marshal(m)
	if err != nil {
		panic(err)
	}

	// {"key1":[{"name":"bingoo"},{"name":"dingoo"}],"key2":"[{\"name\":\"bingoo\"},{\"name\":\"dingoo\"}]","key3":[{"name":"bingoo"},{"name":"dingoo"}]}
	fmt.Println(string(jso))
}

JSON解析库

  1. 流式 JSON 解析库 GoJay aims to be a very fast, JIT stream parser with 0 reflection, low allocation with a friendly API.

Alternate implementations

Popular replacements for standard library packages:

  1. encoding/json -> ffjson, easyjson, jingo (only encoder), etc
  2. net/http
    • fasthttp (but incompatible API, not RFC compliant in subtle ways)
    • httprouter (has other features besides speed; I've never actually seen routing in my profiles)
  3. regexp -> ragel (or other regular expression package)
  4. serialization
    • encoding/gob -> alecthomas/go_serialization_benchmarks
    • protobuf -> gogo/protobuf
    • all serialization formats have trade-offs: choose one that matches what you need
      • Write heavy workload -> fast encoding speed
      • Read-heavy workload -> fast decoding speed
      • Other considerations: encoded size, language/tooling compatibility
    • tradeoffs of packed binary formats vs. self-describing text formats
  5. database/sql -> has tradeoffs that affect performance
    • look for drivers that don't use it: jackx/pgx, crawshaw sqlite, ...
  6. gccgo (benchmark!), gollvm (WIP)
  7. container/list: use a slice instead (almost always)
  8. gojsonq A simple Go package to Query over JSON Data. It provides simple, elegant and fast ODM like API to access, query JSON document
    import "github.com/thedevsaddam/gojsonq"
    
    func main() {
    	const json = `{"name":{"first":"Tom","last":"Hanks"},"age":61}`
    	name := gojsonq.New().FromString(json).Find("name.first")
    	println(name.(string)) // Tom
    }

强制确保类型实现某个接口

Go 语言中,类型实现某个接口 ,只要实现了该接口中所有定义的方法即可,没有像 Java 中的 implements 关键字,是一种契约式精神的体现,或者说鸭子类型。那有没有强制某个类型必须某个接口的写法呢,刚刚在翻阅 jons.RawMessage 中看到了以下两行代码,刚开始感觉迷惑,仔细思考后,便了解了其意图:

var _ Marshaler = (*RawMessage)(nil)
var _ Unmarshaler = (*RawMessage)(nil)

带上下文的代码如下:

// RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can
// be used to delay JSON decoding or precompute a JSON encoding.
type RawMessage []byte

// MarshalJSON returns m as the JSON encoding of m.
func (m RawMessage) MarshalJSON() ([]byte, error) {
	if m == nil {
		return []byte("null"), nil
	}
	return m, nil
}

// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
	if m == nil {
		return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
	}
	*m = append((*m)[0:0], data...)
	return nil
}

var _ Marshaler = (*RawMessage)(nil)
var _ Unmarshaler = (*RawMessage)(nil)

在 for 循环里不要使用 select + time.After 的组合,有坑

本来想给朋友宣称一下,西游可能有 Bug,我都已经被挤下线了,但是还能继续谷歌(假百度),然后随便输入了一个 golang oom 作为搜索条件:

结果就把这个搜索结果页面扔到一边,去干其他事情去了。

等回过头来,打算关闭这个标签页的时候,发现结果 第一条,还是有点兴趣的,就点进去看了,最后有这么一句话:

有经验的 gopher 都知道,在 for 循环里不要使用 select + time.After 的组合,有坑。
当使用 golang 过程中,遇到性能和内存 gc 问题,都可以使用 golang tool pprof 来排查分析问题。

然后,想到自己也经常这么用(for select time.After),很可能踩坑。然后去继续探索了一下,为什么可能会有坑 ,然后将代码中的for select time.After全部重构成 time.Timer + for来预防坑。

Go 编程: 对不起,你的 CPU 泄露了

Don't use time.Tick to prevent leaks

就是因为在循环中,不停的创建新的计时器,而每个计时器都会开启内部协程。再看看计时器函数的官方注释:

// Tick is a convenience wrapper for NewTicker providing access to the ticking
// channel only. While Tick is useful for clients that have no need to shut down
// the Ticker, be aware that without a way to shut it down the underlying
// Ticker cannot be recovered by the garbage collector; it "leaks".
// Unlike NewTicker, Tick will return nil if d <= 0.
func Tick(d Duration) <-chan Time {
	if d <= 0 {
		return nil
	}
	return NewTicker(d).C
}

一些代码检查工具

  1. golangci-lint run 来自这里,安装 GO111MODULE=on go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.16.0
  2. 一些定制 golangci-lint run --exclude="cyclomatic complexity" --exclude-use-default=false --enable=golint --enable=gocyclo --enable=goconst --enable=unconvert ./...
  3. golangci-lint run --enable-all
  4. Staticcheck – a collection of static analysis tools for working with Go code,安装 GO111MODULE=off go get honnef.co/go/tools/cmd/...,运行 staticcheck ./...
  5. 像牛人一样改进你的Go代码
  6. Go Report Card,golang进阶:怎么开发一个热门的开源项目
  7. A tool to list and diagnose Go processes currently running on your system
  8. golang 进阶: go1.12 mod 教程

检查依赖是否有升级

go get -u github.com/psampaz/go-mod-outdated
go list -u -m -json all | go-mod-outdated -direct

example output

+---------------------------------+----------------------------------------------------------------+------------------------------------+--------+------------------+
|             MODULE              |                            VERSION                             |            NEW VERSION             | DIRECT | VALID TIMESTAMPS |
+---------------------------------+----------------------------------------------------------------+------------------------------------+--------+------------------+
| github.com/alecthomas/template  | v0.0.0-20160405071501-a0175ee3bccc                             |                                    | true   | true             |
| github.com/bingoohuang/gou      | v0.0.0-20190604082926-bf3d9b2b55aa5840c442284656ed6b15aedc5a25 | v0.0.0-20190604082926-be6100942b5a | true   | false            |
| github.com/bingoohuang/now      | v0.0.0-20190604021600-70970d3ad0e7                             |                                    | true   | true             |
| github.com/bingoohuang/statiq   | v0.2.1                                                         |                                    | true   | true             |
| github.com/dgraph-io/badger     | v2.0.0-rc.2+incompatible                                       | v1.5.4                             | true   | false            |
| github.com/gchaincl/dotsql      | v0.1.0                                                         |                                    | true   | true             |
| github.com/gin-contrib/sessions | v0.0.0-20190226023029-1532893d996f                             | v0.0.0-20190512062852-3cb4c4f2d615 | true   | true             |
| github.com/gin-gonic/gin        | v1.4.0                                                         |                                    | true   | true             |
| github.com/go-sql-driver/mysql  | v1.4.1                                                         |                                    | true   | true             |
| github.com/lib/pq               | v1.0.0                                                         | v1.1.1                             | true   | true             |
| github.com/mattn/go-sqlite3     | v1.10.0                                                        |                                    | true   | true             |
| github.com/patrickmn/go-cache   | v2.1.0+incompatible                                            |                                    | true   | true             |
| github.com/pkg/errors           | v0.8.1                                                         |                                    | true   | true             |
| github.com/sirupsen/logrus      | v1.4.2                                                         |                                    | true   | true             |
| github.com/spf13/pflag          | v1.0.3                                                         |                                    | true   | true             |
| github.com/spf13/viper          | v1.3.2                                                         | v1.4.0                             | true   | true             |
| github.com/stretchr/testify     | v1.3.0                                                         |                                    | true   | true             |
| github.com/swaggo/swag          | v1.4.1                                                         | v1.5.0                             | true   | true             |
| github.com/thoas/go-funk        | v0.4.0                                                         |                                    | true   | true             |
+---------------------------------+----------------------------------------------------------------+------------------------------------+--------+------------------+

Golang 中的对象健美操

  • One level of indentation per method.
  • Don't use the ELSE keyword.
  • Wrap all primitives and Strings in classes.
  • First class collections.
  • One dot per line.
  • Don't abbreviate.
  • Keep all classes less than 50 lines.
  • No classes with more than two instance variables.
  • No getters or setters.

Go linux 安装

下载,解压缩

[vagrant@bogon ~]$ curl -O https://dl.google.com/go/go1.12.5.linux-amd64.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  122M  100  122M    0     0   349k      0  0:05:57  0:05:57 --:--:--  356k
[vagrant@bogon ~]$ tar -xzf go1.12.5.linux-amd64.tar.gz
[vagrant@bogon ~]$ ls
base.sh  cleanup.sh  go  go1.12.5.linux-amd64.tar.gz  puppet.sh  vagrant.sh  virtualbox.sh  zerodisk.sh

设置环境变量 PATH

[vagrant@bogon ~]$ echo "export PATH=\$PATH:/home/vagrant/go/bin" >> ~/.bashrc; source ~/.bashrc
[vagrant@bogon ~]$ go version
go version go1.12.5 linux/amd64

Go1.13 将正式开始 Go2 开发历程

首先是执行 go get golang.org/dl/gotip 安装 tip 的辅助命令,然后通过执行 gotip download下载真正的 tip 版本工具。下载完成之后,就可以通过 totip 命令来编译和运行 Go 程序了。

➜  kerb git:(master) ✗ go get golang.org/dl/gotip
go: finding golang.org/dl/gotip latest
go: finding golang.org/dl latest
go: downloading golang.org/dl v0.0.0-20190507014322-219d744c5398
go: extracting golang.org/dl v0.0.0-20190507014322-219d744c5398
➜  kerb git:(master) ✗ gotip download
Cloning into '/Users/bingoobjca/sdk/gotip'...
remote: Counting objects: 9475, done
remote: Finding sources: 100% (9475/9475)
remote: Total 9475 (delta 1010), reused 5862 (delta 1010)
package main
Receiving objects: 100% (9475/9475), 22.11 MiB | 363.00 KiB/s, done.
Resolving deltas: 100% (1010/1010), done.
Checking out files: 100% (8598/8598), done.
HEAD is now at f2a4c13 errors: clarify doc for As
Building Go cmd/dist using /usr/local/go.
Building Go toolchain1 using /usr/local/go.
Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1.
Building Go toolchain2 using go_bootstrap and Go toolchain1.
Building Go toolchain3 using go_bootstrap and Go toolchain2.
Building packages and commands for darwin/amd64.
---
Installed Go for darwin/amd64 in /Users/bingoobjca/sdk/gotip
Installed commands in /Users/bingoobjca/sdk/gotip/bin
Success. You may now run 'gotip'!
➜  kerb git:(master) ✗ gotip version
go version devel +f2a4c13 Tue Jun 11 21:50:05 2019 +0000 darwin/amd64

GOPROXY

GOPROXY

# Enable the go modules feature
export GO111MODULE=on
# Set the GOPROXY environment variable
export GOPROXY=https://goproxy.io

proxy.golang.org

export GO111MODULE=on
export GOPROXY=https://proxy.golang.org

from go 1.13

GOPROXY=direct,https://127.0.0.1:12333,https://goproxy.cn,https://goproxy.io,https://mirrors.aliyun.com/goproxy,https://athens.azurefd.net
go env -w GOSUMDB="off"

Go 语言诞生时,我们称它为系统编程语言,我有点遗憾,因为很多人因此认为它是一种操作系统编写语言。我们应该称它为服务编写语言,这是我们真正想做的。现在我想明白了,Go 是云基础架构语言,因为系统编程的另一个定义是云中运行的东西。

When we first announced Go, we called it a systems programming language, and I slightly regret that because a lot of people assumed it was an operating systems writing language. What we should have called it is a server writing language, which is what we really thought of it as. Now I understand that what we have is a cloud infrastructure language. Another definition of systems programming is the stuff that runs in the cloud.

-- Rob Pike

今天我探索了一下 golang 的两个方面:

  1. 类似于 Java 中有很好的 Builder 模式
  2. Getter 和 Setter 的命名约定

然后我发现了函数选项(Functional Options)模式, GIST上有一个最小的例子

函数选项模式是由Rob Pike提出,并由Dave Cheney等推广开,它优雅地解决了go语言中默认参数问题。

下面总结一下,函数选项模式有哪些优点:

  1. 支持默认参数:不必向结构体参数那样,不使用时仍必须传递一个空的struct值
  2. 代码简洁:即使是像go-micro这种支持如此繁多选项,代码也很美观
  3. 扩展性好:增加新的选项只需少量代码

推而广之:类似结构体中变量的赋值都可以效仿之。

  1. Using functional options instead of method chaining in Go中,以gorm为示例,使用函数选项为例,改造了gorm的用法
  2. Fluent Middleware in golang 使用了类似的Fluent模式。
  3. 类型安全的Reusable and type-safe options for Go API

溜开源项目

  1. gorm, GORM 中文文档
  2. Converts a database into gorm structs and RESTful api
  3. Converts a mysql table into a golang struct
  4. go-queryset 100% type-safe ORM for Go with code generation and MySQL/PostgreSQL/Sqlite3/SQL Server support. GORM adapted.
  5. Loukoum is a simple SQL Query Builder
  6. gosql based on sqlx, It's simple and keep simple
  7. go-tagexpr An interesting go struct tag expression syntax for field validation, etc.
  8. graceful reload golang http server, zero downtime, compatible with systemd, supervisor
  9. go-daemon Build Status GoDoc Library for writing system daemons in Go.,涉及lockfile
  10. tableflip Graceful process restarts in Go
  11. MinIO is a high performance object storage server compatible with Amazon S3 APIs
  12. RadonDB is an open source, cloud-native MySQL database for building global, scalable cloud services

创建的对象,里面已经启动了一个后台的死循环的 go 协程,当对象不再被使用时,因为背后的go协程还一直在跑,导致对象不能被gc回收,咋办?,参见 go-cache 的实现

func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
	items := make(map[string]Item)
	return newCacheWithJanitor(defaultExpiration, cleanupInterval, items)
}

func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache {
	c := newCache(de, m)
	// This trick ensures that the janitor goroutine (which--granted it
	// was enabled--is running DeleteExpired on c forever) does not keep
	// the returned C object from being garbage collected. When it is
	// garbage collected, the finalizer stops the janitor goroutine, after
	// which c can be collected.
	C := &Cache{c}
	if ci > 0 {
		runJanitor(c, ci)
		runtime.SetFinalizer(C, stopJanitor)
	}
	return C
}

func runJanitor(c *cache, ci time.Duration) {
	j := &janitor{
		Interval: ci,
		stop:     make(chan bool),
	}
	c.janitor = j
	go j.Run(c)
}

以库为驱动开发的 Golang 工程结构,摘自Library driven development

Moving the main.go file out of your root allows you to build your application from the perspective of a library. Your application binary is simply a client of your application’s library.
Sometimes you might want users to interact in multiple ways so you create multiple binaries.
For example, if you had an “adder” package that that let users add numbers together, you may want to release a command line version as well as a web version.
You can easily do this by organizing your project like this:

adder/
  adder.go
  cmd/
    adder/
      main.go
    adder-server/
      main.go

Users can install your “adder” application binaries with “go get” using an ellipsis:

$ go get github.com/benbjohnson/adder/...

And voila, your user has “adder” and “adder-server” installed!

稍微复杂一点的结构,摘自 Go project structure to produce library and cli with the same name in single repository

Of course if your project is more complex, you may create further packages under the project root, and it may have multiple commands (multiple main packages), e.g.:

host.com/project/
    cmd/
        project/
            project.go
        prjtool/
            prjtool.go
    packagex/
        x.go
    packagey/
        y.go
    filea.go
    fileb.go

An example following this layout is the very popular Go Delve debugger (4.5k stars currently).

Passing callbacks and pointers to Cgo

This post discusses an end-to-end example that covers:

  1. Basic usage of Cgo, including linking a custom C library into the Go binary.
  2. Passing structs from Go to C.
  3. Passing Go functions to C and arranging C to call them back later.
  4. Safely passing arbitrary Go data to C code, which can later pass it back to the Go callbacks it invokes.

The full source code for this example is available on Github. cgo-callback.zip

宽进严出原则:

Be conservative in what you send, be liberal in what you accept

  • Robustness
  • Principle
  • 在 golang 上的应用:

Return concrete types, receive interfaces as parameters

选哪个?

  1. func New() *os.File
  2. func New() io.ReadWriteCloser
  3. func New() io.Writer
  4. func New() interface{}

根据 宽进严出: 选 func New() *os.File

摘自 理解go interface

看 goroutine 的数量

➜  golang-trial git:(master) ✗ GODEBUG=schedtrace=1000 gohttpd
SCHED 0ms: gomaxprocs=12 idleprocs=10 threads=4 spinningthreads=1 idlethreads=0 runqueue=0 [0 0 0 0 0 0 0 0 0 0 0 0]
SCHED 1009ms: gomaxprocs=12 idleprocs=12 threads=7 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0 0 0 0 0]
SCHED 2013ms: gomaxprocs=12 idleprocs=12 threads=7 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0 0 0 0 0]
SCHED 3019ms: gomaxprocs=12 idleprocs=12 threads=7 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0 0 0 0 0]
SCHED 4026ms: gomaxprocs=12 idleprocs=12 threads=7 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0 0 0 0 0]
SCHED 5033ms: gomaxprocs=12 idleprocs=12 threads=7 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0 0 0 0 0]
SCHED 6036ms: gomaxprocs=12 idleprocs=0 threads=38 spinningthreads=0 idlethreads=21 runqueue=152 [4 1 11 0 2 1 3 10 4 5 1 9]
SCHED 7042ms: gomaxprocs=12 idleprocs=0 threads=38 spinningthreads=0 idlethreads=19 runqueue=209 [12 7 15 2 18 15 0 13 19 3 2 16]
SCHED 8046ms: gomaxprocs=12 idleprocs=0 threads=38 spinningthreads=0 idlethreads=19 runqueue=170 [0 12 10 8 4 5 0 3 4 5 1 5]
SCHED 9055ms: gomaxprocs=12 idleprocs=0 threads=38 spinningthreads=0 idlethreads=21 runqueue=234 [3 0 15 9 3 11 1 10 5 6 10 3]
SCHED 10056ms: gomaxprocs=12 idleprocs=0 threads=38 spinningthreads=0 idlethreads=19 runqueue=221 [1 13 15 9 5 2 0 0 8 12 4 10]
SCHED 11058ms: gomaxprocs=12 idleprocs=0 threads=38 spinningthreads=0 idlethreads=18 runqueue=159 [9 12 4 13 4 9 0 10 11 1 1 12]
SCHED 12061ms: gomaxprocs=12 idleprocs=0 threads=38 spinningthreads=0 idlethreads=19 runqueue=204 [0 16 3 18 7 16 3 2 17 12 18 13]
SCHED 13062ms: gomaxprocs=12 idleprocs=0 threads=38 spinningthreads=1 idlethreads=23 runqueue=237 [16 12 3 13 7 6 0 1 1 15 9 21]
SCHED 14062ms: gomaxprocs=12 idleprocs=0 threads=38 spinningthreads=0 idlethreads=17 runqueue=201 [16 1 1 11 14 2 5 10 2 0 0 16]
SCHED 15072ms: gomaxprocs=12 idleprocs=0 threads=38 spinningthreads=0 idlethreads=22 runqueue=26 [0 0 0 0 0 0 0 0 0 0 0 0]
SCHED 16080ms: gomaxprocs=12 idleprocs=0 threads=38 spinningthreads=0 idlethreads=21 runqueue=202 [10 16 18 0 12 0 19 13 9 17 0 1]
SCHED 17087ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=15 runqueue=167 [8 3 12 14 14 14 4 17 1 6 15 6]
SCHED 18092ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=22 runqueue=209 [18 4 0 17 4 9 0 10 0 3 5 16]
SCHED 19093ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=21 runqueue=198 [16 17 3 6 4 1 14 6 18 18 10 21]
SCHED 20093ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=24 runqueue=232 [7 12 13 5 3 7 5 2 8 1 9 8]
SCHED 21100ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=24 runqueue=206 [16 1 14 9 16 7 16 13 5 1 8 4]
SCHED 22110ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=23 runqueue=249 [8 6 8 3 10 9 12 8 6 11 0 3]
SCHED 23113ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=20 runqueue=223 [0 5 17 2 5 3 0 0 0 14 9 2]
SCHED 24116ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=1 idlethreads=22 runqueue=219 [1 0 9 3 1 2 13 10 1 1 18 12]
SCHED 25124ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=23 runqueue=199 [14 12 15 6 2 1 5 0 11 1 15 0]
SCHED 26126ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=21 runqueue=169 [1 12 6 11 8 5 8 5 5 10 6 4]
SCHED 27126ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=20 runqueue=232 [4 5 9 0 1 1 1 1 5 1 11 4]
SCHED 28129ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=21 runqueue=243 [9 11 7 3 7 14 3 12 2 12 12 1]
SCHED 29136ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=20 runqueue=58 [0 4 0 1 0 0 2 0 0 3 0 0]
SCHED 30144ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=22 runqueue=153 [0 9 7 1 4 2 10 2 5 0 0 1]
SCHED 31151ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=22 runqueue=216 [10 4 9 5 14 7 1 10 7 18 8 5]
SCHED 32159ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=24 runqueue=203 [5 8 2 0 5 1 1 14 3 0 14 12]
SCHED 33164ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=22 runqueue=212 [14 13 5 19 0 13 1 8 15 9 11 2]
SCHED 34174ms: gomaxprocs=12 idleprocs=0 threads=40 spinningthreads=0 idlethreads=23 runqueue=266 [6 4 12 12 1 0 8 4 0 3 0 3]
  • sched:每一行都代表调度器的调试信息,后面提示的毫秒数表示启动到现在的运行时间,输出的时间间隔受 schedtrace 的值影响。
  • gomaxprocs:当前的 CPU 核心数(GOMAXPROCS 的当前值)。
  • idleprocs:空闲的处理器数量,后面的数字表示当前的空闲数量。
  • threads:OS 线程数量,后面的数字表示当前正在运行的线程数量。
  • spinningthreads:自旋状态的 OS 线程数量。
  • idlethreads:空闲的线程数量。
  • runqueue:全局队列中中的 Goroutine 数量,而后面的 [0 0 1 1] 则分别代表这 4 个 P 的本地队列正在运行的 Goroutine 数量。

参考:

  1. 用 GODEBUG 看调度跟踪

测试相关

TestMain

在写测试时,有时需要在测试之前或之后进行额外的设置(setup)或拆卸(teardown);有时,测试还需要控制在主线程上运行的代码。为了支持这些需求,testing 提供了 TestMain 函数:

func TestMain(m *testing.M)
package mytestmain

import (  
    "flag"
    "fmt"
    "os"
    "testing"
)

var db struct {  
    Dns string
}

func TestMain(m *testing.M) {
    db.Dns = os.Getenv("DATABASE_DNS")
    if db.Dns == "" {
        db.Dns = "root:123456@tcp(localhost:3306)/?charset=utf8&parseTime=True&loc=Local"
    }

    flag.Parse()
    exitCode := m.Run()

    db.Dns = ""

    // 退出
    os.Exit(exitCode)
}

func TestDatabase(t *testing.T) {
    fmt.Println(db.Dns)
}

参数化测试

func TestAdd(t *testing.T) {
    tests := []struct{
        name     string
        first    int64
        second   int64
        expected int64
    } {
        {
            name:     "HappyPath":
            first:    2,
            second:   3,
            expected: 5,
        },
        {
            name:     "NegativeNumber":
            first:    -1,
            second:   -1,
            expected: -2,
        },
    }
    
    for _, test := range tests {
        t.Run(test.name, func(t *testing.T) {
            assert.Equal(t, test.expected, Add(test.first, test.second))
        })
    }
}

测试集

import (
    "testing"
    "github.com/stretchr/testify/suite"
)

type ExampleTestSuite struct {
    suite.Suite
    VariableThatShouldStartAtFive int
}

func (suite *ExampleTestSuite) SetupTest() {
    suite.VariableThatShouldStartAtFive = 5
}

func (suite *ExampleTestSuite) TestExample() {
    suite.Equal(suite.VariableThatShouldStartAtFive, 5)
}

func TestExampleTestSuite(t *testing.T) {
    suite.Run(t, new(ExampleTestSuite))
}

BDD

var _ = Describe("Book", func() {
    var (
        book Book
        err error
    )

    BeforeEach(func() {
        book, err = NewBookFromJSON(`{
            "title":"Les Miserables",
            "author":"Victor Hugo",
            "pages":1488
        }`)
    })

    Describe("loading from JSON", func() {
        Context("when the JSON fails to parse", func() {
            BeforeEach(func() {
                book, err = NewBookFromJSON(`{
                    "title":"Les Miserables",
                    "author":"Victor Hugo",
                    "pages":1488oops
                }`)
            })

            It("should return the zero-value for the book", func() {
                Expect(book).To(BeZero())
            })

            It("should error", func() {
                Expect(err).To(HaveOccurred())
            })
        })
    })
})

Mock

  1. 使用 gomock 提供的 mockgen 工具命令

  2. 使用 sqlmock 来模拟数据库的连接

  3. httpmock 就是一个用于 Mock 所有 HTTP 依赖的包,它使用模式匹配的方式匹配 HTTP 请求的 URL,在匹配到特定的请求时就会返回预先设置好的响应。

  4. 猴子补丁其实就是一个大杀器了,bouk/monkey 能够通过替换函数指针的方式修改任意函数的实现,所以如果上述的几种方法都不能满足我们的需求,我们就只能够通过猴子补丁这种比较 hack 的方法 Mock 依赖了,:

    func main() {
    	monkey.Patch(fmt.Println, func(a ...interface{}) (n int, err error) {
    		s := make([]interface{}, len(a))
    		for i, v := range a {
    			s[i] = strings.Replace(fmt.Sprint(v), "hell", "*bleep*", -1)
    		}
    		return fmt.Fprintln(os.Stdout, s...)
    	})
    	fmt.Println("what the hell?") // what the *bleep*?
    }

    不要在单元测试之外的地方使用猴子补丁,我们应该只在必要的时候使用这种方法,例如依赖的第三方库没有提供 interface 或者修改 time.Now 以及 rand.Int63n 等内置函数的返回值用于测试时。

    从理论上来说,通过猴子补丁这种方式我们能够在运行时 Mock Go 语言中的一切函数,这也为我们提供了单元测试 Mock 依赖的最终解决方案。

go module upgrades dependencies:

  • go get -u (without any arguments) now only upgrades the direct and indirect dependencies of your current package, and no longer examines your entire module.
  • go get -u ./... from your module root upgrades all the direct and indirect dependencies of your module, and now excludes test dependencies.
  • go get -u -t ./... is similar, but also upgrades test dependencies.

Go Profiling and Optimization.pptx

一文读懂 Go profiling 和性能优化

火焰图进行性能分析.pptx

GO语言的历史,非常好的演讲,值得一看。

  1. Hugo作者演讲总结:Go语言的遗产
  2. The Legacy Of Go

编程语言发展的四波浪潮:

  1. 第一波浪潮:语言扩张 - 巴别塔
    特征:多样化。很久以前,语言是多种多样的,并在在思想、方法和意见等方面体现出多样性。
    2 第二波浪潮:语言的标准化
    特征:快速、复杂且对开发不友好。语言的标准化发生了数十年。到2000年代,事情开始停滞。他们融合为两个阵营:Java/JVM和C/CLR。C++、Java、C#都非常相似。
  2. 第三波浪潮:脚本语言
    特征:慢、不安全但对开发友好。脚本语言作为对上述语言的复杂性和痛苦的回应而应运而生。它们开发快速而松散,对开发人员友好,但缺乏性能和安全性。
  3. 第四波浪潮:恢复
    特征:快速、安全、对开发人员友好

Go 恢复了早期语言的简单性和灵活性,增加了现代语言的安全性和开发友好性。Go以一种非常真实的方式复兴了许多伟大的想法,这些想法终于准备就绪。

Go 给人的感觉就像是来自60年代,70年代,80年代,90年代,00s,10年代的语言……Steve Francia 2019

Go 的设计哲学

原则1:进化不是革命(Evolution not revolution), 大多数思想都来自先前的思想
大多数思想根本不是新事物
进化不是革命:新语言应该巩固而不是发明新特性

原则2:等待良好的设计, No是暂时的,Yes是永远的(Waiting for Good design No is temporary, Yes is forever )。
在Go的整个历史中,有很多这样的实例。通常的想法是,在设计语言时,不会出现“撤消(undo)”的情况。如果您今天说“No”,那么您明天总是可以说“Yes”,但是如果今天您说“Yes”,那么您将在很长一段时间或永远被它“困”住…。

如有疑问,请将其排除在外。- Joshua Bloch:关于设计的对话– 2002

原则3: 应该使一切都尽可能简单,但不要过于简单(Consensus driven design Everything should be made as simple as possible, but no simpler.)。-爱因斯坦

当我们三个人开始时,这纯粹是研究。…我们从一个想法开始,即我们三个人都必须针对该语言的每个特性进行讨论,因此,无论出于何种原因,都不会在该语言中放入多余的垃圾。- 肯·汤普森(Ken Thompson)访谈– 2011年,肯从Bell Labs学习了这种做法
有两种构建软件设计的方法。一种方法是使其变得如此简单,以至于显然没有缺陷。另一种方法是使其变得如此复杂,以至于没有明显的缺陷。- 托尼·霍尔(Tony Hoare)皇帝的旧衣服-1981年,Go采取了第一种方法,而大多数其他语言都采用第二种方法。

快速迭代期待(Rapid iteration)并实现大规模改变 最后一个原则是快速迭代的原则。

当您处于语言的设计阶段时,您将需要进行频繁且有时是巨大的更改。朝着这个期望前进,并围绕它建立您的流程。

我的看法:明天将要发布的代码选择 Go,在未来五年内保持不变的代码选择 Rust。—Grzegorz Nosek

如果你想加快开发速度,也许是因为你要编写许多不同的服务,或者你有庞大的开发团队,那么 Go 是你选择的语言。Go 为你提供了一流的并发性,并且不容许不安全的内存访问(Rust 也不容忍),但不会强迫你管理每个最后的细节。Go 是快速而强大的工具,但是它避免了使开发人员陷入困境,而专注于简单性和统一性。另一方面,如果需要拧紧块性能,那么 Rust 应该是你的选择。—Andrew Lader[23]

来自 也许是最客观、全面的比较 Rust 与 Go:都想把 Rust 也学一下原文链接

Find your leaf packages loov/goda:GoDependencyAnalysistoolkit

  1. go get github.com/loov/goda
  2. goda graph ./... | dot -Tsvg -o graph.svg

Golang 枚举生成工具

enumerstringer 的fork上增强的版本

  1. go get github.com/alvaroloes/enumer
  2. enumer -type=Pill -json -transform=snake
  3. stringer的用法参考, only generates String() methods, leaving MarshalText() and UnmarshalText() unimplemented.

有一种未经证实的说法:

Go 诞生于 C++ 程序的漫长构建过程中。

如果C++编译很快,那么Robert Griesemer、Rob Pike和Ken Thompson这三位大佬也没有闲暇时间一起喝着咖啡并决定是时候设计一门新语言了。的确,Go语言诞生后,其简洁的语法、极速地构建、新颖的并发结构、体验优良的工具链以及完成度不低的标准库吸引了很多C/C++程序员转型成为Gopher并开始重度使用Go

来自重度使用 Go 的“后遗症“,你有吗?

  1. https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html
  2. https://mikespook.com/2012/06/翻译少是指数级的多/

回到 2007 年 9 月,我在一个巨大的 Google C++ 程序(就是你们都用过的那个)上做一些琐碎但是很核心的工作,我在那个巨大的分布式集群上需要花大约 45 分钟进行编译。收到一个通知说 Google 雇佣的一对为 C++ 标准化委员会工作的夫妇将会做一场报告。收到一个通知说几个受雇于 Google 的为 C++ 标准化委员会工作的人将会做一场报告。他们将向我们介绍那时还被称作 C++0x(就是现在众所周知的 C++11)中将会有哪些改进。

在长达一个小时的报告中,我们听说了诸如有已经在计划中的 35 个特性之类的事情。事实上有更多,但仅有 35 个特性在报告中进行了描述。当然一些特性很小,但是意义重大,值得在报告中提出。一些非常微妙和难以理解,如左右值引用(rvalue references),还有一些是 C++ 特有的,如可变参数模板(variadic templates),还有一些就是发疯,如用户定义数据标识(user-defined literals)。

这时我问了自己一个问题:C++ 委员会真得相信 C++ 的问题在于没有足够的特性?肯定的说,在另一个 Ron Hardin 的玩笑中,简化语言的成就远远大于添加功能。当然这有点可笑,不过请务必记住这个思路。

Different Thread Models

There are three different threading models we can see Mx1, 1x1, MxN

Go language implemented MxN with three basic primitive entities

  • G Structure — A G struct represents a single goroutine, stack, current stack, stack gaurd, pointer to code (initial function), parameters, goID
  • P Structure — Abstraction to the processor in Go runtime. It holds the Context to run Go routine.
  • M Structure — The M struct is the Go runtime’s representation of an OS thread, global queue of G’s, the G that it is currently running, its own cache, and a handle to the scheduler.

FROM Different Threading Models — Why I Feel Go Threading Is Better

Go Slice Tricks Cheat Sheet

应该有很多人受不了 err 的反复判断,封装各种 err 处理,达到简化的目的,其中一种实现 ErrorFlow Declarative error handling for Go.

func GzipFile(dstFilename string, srcFilename string) (err error) {
	// defer IfError()... creates and configures
	// ErrorFlow error handler for this function.
	// When any of Check* functions encounters non-nil error
	// it immediately sends error to this handler
	// unwinding all stacked defers.
	errWrapper := errf.WrapperFmtErrorw("error compressing file")
	defer errf.IfError().ReturnFirst().LogIfSuppressed().Apply(errWrapper).ThenAssignTo(&err)

	errf.CheckCondition(len(dstFilename) == 0, "dst file should be specified")
	errf.CheckCondition(len(srcFilename) == 0, "src file should be specified")

	reader := errf.Io.CheckReadCloser(os.Open(srcFilename))
	defer errf.With(errWrapper).Log(reader.Close())

	writer := errf.Io.CheckWriteCloser(os.Create(dstFilename))
	defer errf.Handle().OnAnyErrOrPanic(func() { os.Remove(dstFilename) })
	defer errf.CheckErr(writer.Close())

	gzipWriter := gzip.NewWriter(writer)
	defer errf.CheckErr(gzipWriter.Close())

	return errf.CheckDiscard(io.Copy(gzipWriter, reader))
}

vendor 打包编译

开发机上

$ cd app
$ go mod download -v
$ go mod vendor
$ cd ..
$ tar --exclude .git --exclude .idea -czf app.tar.gz app

编译机上

$ tar zxf app.tar.gz
$ cd app
$ go build -mod vendor -o app   -ldflags=' -w -s '

给 expvarmon 插上数据持久化的 翅膀

package main

import (
	_ "expvar"
	"net/http"
)

func main() {
	http.ListenAndServe(":8100", nil)
}
  1. go install github.com/divan/expvarmon@latest
  2. expvarmon -ports="8100"

Serve embedded filesystem from root path of URL

package main

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

//go:embed static
var embeddedFS embed.FS

func main() {
    serverRoot, err := fs.Sub(embeddedFS, "static")
    if err != nil {
        log.Fatal(err)
    }

    http.Handle("/", http.FileServer(http.FS(serverRoot)))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

go 可执行文件分析工具 redress

The redress software is a tool for analyzing stripped Go binaries compiled with the Go compiler. It extracts data from the binary and uses it to reconstruct symbols and performs analysis. It essentially tries to "re-dress" a "stripped" binary.

[2021-05-05 22:16:16.837] ❯ redress -help
Usage of redress:
  -compiler
    	Print information
  -filepath
    	Include file path for packages
  -force-version string
    	Forcing and using the given version when analyzing
  -interface
    	Print interfaces
  -method
    	Print type's methods
  -pkg
    	List packages
  -src
    	Print source tree
  -std
    	Include standard library packages
  -struct
    	Print structs
  -type
    	Print all type information
  -unknown
    	Include unknown packages
  -vendor
    	Include vendor packages
  -version
    	Print redress version
[2021-05-05 22:15:51.625] ❯ go install -ldflags="-s -w" ./...

[2021-05-05 22:16:01.530] ❯ ls -lh ~/go/bin/shorturl
-rwxr-xr-x  1 bingoo  staff    15M  5  5 22:16 /Users/bingoo/go/bin/shorturl
[2021-05-05 22:15:22.711] ❯ redress -src ~/go/bin/shorturl
Package main: /Users/bingoo/GitHub/shorturl
File: main.go
	main Lines: 12 to 22 (10)
Package github.com/bingoohuang/shorturl/pkg: /Users/bingoo/GitHub/shorturl/pkg
File: <autogenerated>
	(*noCache)DeactivateURL Lines: 1 to 1 (0)
	(*noCache)SavePopularURL Lines: 1 to 1 (0)
	(*RedisCache)DeactivateURL Lines: 1 to 1 (0)
	(*noCache)LookupURL Lines: 1 to 1 (0)
	(*RedisCache)SavePopularURL Lines: 1 to 1 (0)
	(*Body)Merge Lines: 1 to 1 (0)
	(*URLInput)ValidateExpiry Lines: 1 to 1 (0)
	(*RedisCache)LookupURL Lines: 1 to 1 (0)
	(*URL)IsActive Lines: 1 to 1 (0)
	(*URLInput)GetExpiresOn Lines: 1 to 1 (0)
File: admin.go
	ListURLsFilteredFromRequest Lines: 9 to 21 (12)
	ListURLsFiltered Lines: 21 to 46 (25)
	DeleteURLFromRequest Lines: 46 to 53 (7)
	DeleteURLByShortCode Lines: 53 to 65 (12)
File: client.go
	CreateURLShortCodeFromRequest Lines: 13 to 25 (12)
	CreateURLShortCode Lines: 25 to 55 (30)
	LookupOriginURL Lines: 55 to 76 (21)
	IncrementHits Lines: 76 to 87 (11)
	ValidateURLInput Lines: 87 to 115 (28)
	getUniqueShortCode Lines: 115 to 133 (18)
	isShortCodeAvail Lines: 133 to 143 (10)
	getShortCodeByOriginURL Lines: 143 to 153 (10)
	mapKeywords Lines: 153 to 162 (9)
File: config.go
	init Lines: 28 to 28 (0)
	createDB Lines: 49 to 74 (25)
	init0 Lines: 74 to 90 (16)
	init.0func1 Lines: 75 to 79 (4)
	createEnvName Lines: 90 to 100 (10)
File: controller.go
	ListURLs Lines: 10 to 22 (12)
	DeleteShortURL Lines: 22 to 33 (11)
	CreateShortURL Lines: 33 to 47 (14)
	NotFound Lines: 47 to 53 (6)
	ServeShortURL Lines: 53 to 68 (15)
File: model.go
	URLIsActive Lines: 55 to 100 (45)
	(*URLInput)Validate Lines: 100 to 142 (42)
	URLInputValidateExpiry Lines: 142 to 164 (22)
	URLInputGetExpiresOn Lines: 164 to 173 (9)
	URLFilterGetOffset Lines: 173 to 183 (10)
File: redis.go
	noCacheLookupURL Lines: 19 to 20 (1)
	noCacheDeactivateURL Lines: 20 to 21 (1)
	noCacheSavePopularURL Lines: 21 to 29 (8)
	CreateRedisCache Lines: 29 to 58 (29)
	CreateRedisCachefunc1 Lines: 36 to 37 (1)
	RedisCacheLookupURL Lines: 58 to 110 (52)
	RedisCacheDeactivateURL Lines: 82 to 93 (11)
	RedisCacheSavePopularURL Lines: 93 to 118 (25)
	hasURL Lines: 104 to 110 (6)
File: router.go
	init1 Lines: 15 to 22 (7)
	AssetsRewrite Lines: 22 to 44 (22)
	AssetsRewritefunc1 Lines: 23 to 67 (44)
	glob.func1 Lines: 34 to 36 (2)
	locateHandler Lines: 44 to 66 (22)
	RegisterHandlers Lines: 66 to 81 (15)
	RegisterHandlersfunc1 Lines: 67 to 83 (16)
	Recover Lines: 81 to 94 (13)
	Recoverfunc1 Lines: 82 to 95 (13)
	Recover.func11 Lines: 83 to 85 (2)
	AdminAuth Lines: 94 to 107 (13)
	AdminAuthfunc1 Lines: 95 to 102 (7)
	validateAdminToken Lines: 107 to 122 (15)
File: util.go
	RandomString Lines: 17 to 33 (16)
	BodyMerge Lines: 33 to 43 (10)
	JSON Lines: 43 to 49 (6)

延长变量的生命周期 runtime.KeepAlive(v)

2019年的一篇文章:https://medium.com/a-journey-with-go/go-keeping-a-variable-alive-c28e3633673a

package main

import (
	"io/ioutil"
	"log"
	"os"
	"runtime"
	"syscall"
)

type File struct{ d int }

func main() {
	file, err := ioutil.TempFile("", "keepalive")
	if err != nil {
		log.Fatal(err)
	}
	file.Write([]byte("keepalive"))
	file.Close()
	defer os.Remove(file.Name())

	p := openFile(file.Name())
	content := readFile(p.d)

	// Ensure p is not finalized until Read returns
	// runtime.KeepAlive(p)

	println("Here is the content: " + content)
}

func openFile(path string) *File {
	d, err := syscall.Open(path, syscall.O_RDONLY, 0)
	if err != nil {
		panic(err)
	}

	p := &File{d}
	runtime.SetFinalizer(p, func(p *File) {
		syscall.Close(p.d)
	})

	return p
}

func readFile(descriptor int) string {
	doSomeAllocation()

	var buf [1000]byte
	_, err := syscall.Read(descriptor, buf[:])
	if err != nil {
		panic(err)
	}

	return string(buf[:])
}

func doSomeAllocation() {
	var a *int

	// memory increase to force the GC
	for i := 0; i < 10000000; i++ {
		i := 1
		a = &i
	}

	_ = a
}

输出:

panic: device not configured

goroutine 1 [running]:
main.readFile(0x3, 0x43, 0xc000120010)
        /Users/bingoo/GitHub/gogotcha/cmd/keepalive/main.go:51 +0x138
main.main()
        /Users/bingoo/GitHub/gogotcha/cmd/keepalive/main.go:23 +0x176

加上runtime.KeepAlive后(放开对应的注释行),

Here is the content: keepalive

用组合实现继承

继承,直达问题的本质,清晰易懂

type Foo struct {
     Base // 继承
     ...
}
type Foo struct {
     *Base // 虚拟继承
     ...
}

来源,许式伟, Go vs. GoPlus(Go+) 2021-6-27 北京 GopherChina2021

 

介绍了 go 中dig和wire两个DI工具。其中dig是通过运行时反射实现的依赖注入。 而wire是根据自定义的代码,通过命令,生成相应的依赖注入代码,在编译期就完成依赖注入,无需反射机制。 这样的好处是:

  1. 方便排查,如果存在依赖错误,编译时就能发现。而 dig 只能在运行时才能发现依赖错误。
  2. 避免依赖膨胀,wire生成的代码只包含被依赖的,而dig可能会存在好多无用依赖。
  3. 依赖关系静态存在源码,便于工具分析。

为什么把 dig 迁移到 wire

为什么腾讯越来越倾向于使用Go语言?

原因可能会有很多,关于Go语言的特性、优势等。但是最主要的原因,应该是基于两方面的考虑:执行性能&开发效率。

Go 语言以其接近 C 的执行性能和近解释型语言的开发效率,以及近乎于完美的编译速度,已经广泛应用于人工智能、云计算开发、容器虚拟化、大数据开发、 数据分析及科学计算、运维开发、爬虫、游戏开发等领域。

Go语言适用于各个领域行业-前景广阔

Sealed Interfaces 密封接口

看到一种 go 代码,接口里定义一个私有无参无返回值的方法,感觉是一种惯用法,原来是密封接口,保证这个接口不给外部去实现。

// Statement represents a statement.
type Statement interface {
	iStatement()
	SQLNode
}

func (*Union) iStatement()         {}
func (*Select) iStatement()        {}
func (*Insert) iStatement()        {}
func (*Update) iStatement()        {}
func (*Delete) iStatement()        {}
func (*Set) iStatement()           {}
func (*DDL) iStatement()           {}
func (*Show) iStatement()          {}
func (*Use) iStatement()           {}
func (*OtherRead) iStatement()     {}
func (*OtherAdmin) iStatement()    {}
func (*TruncateTable) iStatement() {}

同样的

// SelectStatement any SELECT statement.
type SelectStatement interface {
	iSelectStatement()
	iStatement()
	iInsertRows()
	AddOrder(*Order)
	SetLimit(*Limit)
	SQLNode
}

func (*Select) iSelectStatement()      {}
func (*Union) iSelectStatement()       {}
func (*ParenSelect) iSelectStatement() {}

所有模型都是错误的,但有些模型是有用的

在 go-profiler-notes 上看到一句话: All models are wrong

本质上,所有模型都是错误的,但有些模型是有用的。

--- Box,乔治·EP;Norman R.Draper(1987)。经验模型的建立和响应面,p。424,威利。ISBN 0471810339。

我认为最好通过两部分来分析它的含义:

“所有模型都是错误的”,也就是说,每个模型都是错误的,因为它是对现实的简化。一些模型,特别是在“硬”科学中,只是有点错误。他们无视摩擦或微小物体的引力作用。其他模型有很多错误-他们忽略了更大的事情。在社会科学中,我们忽略了很多。

“但是有些有用”-简化现实可能非常有用。它们可以帮助我们解释,预测和理解宇宙及其所有组成部分。

这不仅在统计上是正确的!地图是一种模型。他们错了。但是好的地图非常有用。其他有用但错误的模型的例子比比皆是。

比例为1:1的完美地图的幻想已被许多作者使用,包括Lewis Carroll,Jorge Luis Borges和Umberto Eco。实际上,这没有用,因为它所映射的区域必然很复杂,而且不容易理解(更不用说将其展开并布置为阅读的尴尬了)。

也许您还可以补充一点,就是模型必须有点错误,因为否则它将无法泛化,因此无法在其他地方应用。有一些答案说明了这一点。但是现在有太多的答案无法全部阅读。

对我来说,真正的见解在于以下方面:

模型不一定非要正确才有用。

不幸的是,在许多科学中,人们常常忘记了模型不一定必须是现实的精确表示即可允许新的发现和预测!

因此,不要浪费您的时间来构建需要对无数变量进行准确测量的复杂模型。真正的天才发明了可以完成这项工作的简单模型。

unsafe.Pointer and uintptr

unsafe.Pointer and uintptr

package main

import "unsafe"

func f() {
	var p uintptr
	for i := range [10]int{} {
		var x = i
		if i == 0 {
			// uintptr类型的临时变量只是一个普通的数字,所以其值不应该被改变。
            // 因此,x 变量在栈上,10次循环,所使用的栈地址不变,因此 p 最后的值是9
			p = uintptr(unsafe.Pointer(&x))
		}
	}
	println(*(*int)(unsafe.Pointer(p))) // 9
}

func g() {
	var p unsafe.Pointer
	for i := range [10]int{} {
		var x = i
		if i == 0 {
			// 从gc视角来看,unsafe.Pointer是一个指向变量的指针,因此当变量被移动是对应的指针也必须被更新
			// 为了跟踪指针更新, x 逃逸到了堆上,10次循环,生成了10个不同的x堆变量, p指向了第1个,其值是0
			p = unsafe.Pointer(&x)
		}
	}
	println(*(*int)(p)) // 0
}

func main() {
	f()
	g()
}
$ go run -gcflags="-m" p.go
# command-line-arguments
./p.go:19:7: moved to heap: x
9
0

从垃圾收集器的视角来看,一个unsafe.Pointer是一个指向变量的指针,因此当变量被移动是对应的指针也必须被更新;但是uintptr类型的临时变量只是一个普通的数字,所以其值不应该被改变。

Go语言圣经(中文版)

此例,即考察了 unsafe.Pointer,uintptr,又考察了变量逃逸。

go1.18 泛型变快了嘛?

Credit: https://unsplash.com/photos/CpkOjOcXdUY

看博客 Go is about to get a whole lot faster

源代码:

  1. deque 无泛型版本
  2. deque 有泛型版本
  3. 二者差异

操作步骤:

  1. cd deque-generic && go1.18beta1 test -run=NONE -bench=. > ../generic.txt
  2. cd deque-non-generic && go1.18beta1 test -run=NONE -bench=. > ../none.txt
  3. cd deque-non-generic && go test -run=NONE -bench=. > ../none-go1.17.6.txt

结果输出:

$ go install golang.org/x/tools/cmd/benchcmp@latest   
  deque more generic.txt 
goos: darwin
goarch: amd64
pkg: github.com/sekoyo/deque
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkPushFront-12           97286768                12.28 ns/op
BenchmarkPushBack-12            142144150               14.43 ns/op
BenchmarkSerial-12              100000000               11.82 ns/op
BenchmarkSerialReverse-12       100000000               11.21 ns/op
BenchmarkRotate-12                 56238            120075 ns/op
BenchmarkInsert-12                 36441            120364 ns/op
BenchmarkRemove-12                107545            119688 ns/op
BenchmarkYoyo-12                    1777            675390 ns/op
BenchmarkYoyoFixed-12               2662            455608 ns/op
PASS
ok      github.com/sekoyo/deque 33.914s
  deque more non.txt    
goos: darwin
goarch: amd64
pkg: github.com/gammazero/deque
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkPushFront-12           25286799                50.48 ns/op
BenchmarkPushBack-12            28099020                41.14 ns/op
BenchmarkSerial-12              25631952                50.65 ns/op
BenchmarkSerialReverse-12       26357466                50.56 ns/op
BenchmarkRotate-12                 40188            119641 ns/op
BenchmarkInsert-12                 28888            120136 ns/op
BenchmarkRemove-12                 87488            123670 ns/op
BenchmarkYoyo-12                     574           2039762 ns/op
BenchmarkYoyoFixed-12                877           1382135 ns/op
PASS
ok      github.com/gammazero/deque      28.086s
  deque benchcmp none.txt generic.txt 
benchcmp is deprecated in favor of benchstat: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat
benchmark                     old ns/op     new ns/op     delta
BenchmarkPushFront-12         50.5          12.3          -75.67%
BenchmarkPushBack-12          41.1          14.4          -64.92%
BenchmarkSerial-12            50.6          11.8          -76.66%
BenchmarkSerialReverse-12     50.6          11.2          -77.83%
BenchmarkRotate-12            119641        120075        +0.36%
BenchmarkInsert-12            120136        120364        +0.19%
BenchmarkRemove-12            123670        119688        -3.22%
BenchmarkYoyo-12              2039762       675390        -66.89%
BenchmarkYoyoFixed-12         1382135       455608        -67.04%
  deque more none-go1.17.6.txt 
goos: darwin
goarch: amd64
pkg: github.com/gammazero/deque
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkPushFront-12           24497521                50.09 ns/op
BenchmarkPushBack-12            32226673                39.35 ns/op
BenchmarkSerial-12              28485412                46.10 ns/op
BenchmarkSerialReverse-12       27771453                46.76 ns/op
BenchmarkRotate-12                 40466            121028 ns/op
BenchmarkInsert-12                 28731            121774 ns/op
BenchmarkRemove-12                 86724            125651 ns/op
BenchmarkYoyo-12                     620           2049784 ns/op
BenchmarkYoyoFixed-12                820           1389294 ns/op
PASS
ok      github.com/gammazero/deque      28.335s

还真是,在 PushFront 上竟然快了 75%,看来等 1.18 正式发布了,还是要香一下的。

Go 可执行文件大小 树图分析

go-binsize-treemap

  1. go install github.com/nikolaydubina/go-binsize-treemap@latest
  2. go tool nm -size <binary finename> | go-binsize-treemap > binsize.svg

在线火焰图

https://flamegraph.com/

Pyroscope Go Playground

有名返回参数的坑

  1. https://mp.weixin.qq.com/s/RpeiByFggXal07awqfT8vA
  2. https://twitter.com/bwplotka/status/1494362886738780165

Bartłomiej Płotka 出的题

package main

func aaa() (done func(), err error) {
	return func() {
		print("aaa: done")
	}, nil
}

func bbb() (done func(), _ error) {
	done, err := aaa()
	return func() {
		print("bbb: surprise!")
		done()
	}, err
}

func main() {
	done, _ := bbb()
	done()
}

这个程序输出结果是什么呢(单选)?

  • 输出 aaa: done
  • 输出 ​bbb: surprise!aaa: done
  • 一直输出,永远不结束
  • 程序最终运行出错

正确答案是:D,程序最终运行出错。一直调用一直爽,直至栈溢出程序崩溃。

他会不断地递归,疯狂输出 “bbb: surprise!”,直至栈溢出,导致程序运行出错,最终中止。

同学就疑惑了,怎么又多出了个递归?

本质上在函数 bbb 执行完毕后, 变量 done 已经变成了一个递归函数。

递归的过程是:函数 bbb 调用变量 done 后,会输出 bbb: surprise! 字符串,然后又调用变量 done。而变量 done 又是这个闭包(匿名函数),从而实现不断递归调用和输出。

总结

这位大佬出的题目,本质上是比较烦人的,其结合了函数返回参数的命名用法。

如果我们把这个函数的返回参数命名去掉,就可以避开这个问题。

详解 Go 中的 rune 类型

https://mp.weixin.qq.com/s/hcrq5fYaQ7FN_2oSMRNjcA

Go 语言把字符分 byte 和 rune 两种类型处理。byte 是类型 unit8 的别名,用于存放占 1 字节的 ASCII 字符,如英文字符,返回的是字符原始字节。rune 是类型 int32 的别名,用于存放多字节字符,如占 3 字节的中文字符,返回的是字符 Unicode 码点值。如下图所示:

s := "Go语言编程"
// byte
fmt.Println([]byte(s)) // 输出:[71 111 232 175 173 232 168 128 231 188 150 231 168 139]
// rune
fmt.Println([]rune(s)) // 输出:[71 111 35821 35328 32534 31243]

docker 编译

Dockerfile:

FROM golang:alpine AS builder
WORKDIR /build
RUN apk add upx
COPY . .
RUN go build -ldflags "-s -w" -o hello hello.go && upx hello

FROM alpine
WORKDIR /build
COPY --from=builder /build/hello /build/hello
CMD ["./hello"]
  1. docker build -t hello:v1 .
  2. dive hello:v1 .
$ docker run -it --rm hello:v1
hello world!
$ docker run -it --rm hello:v1 ls -lh /build
total 332K
-rwxr-xr-x    1 root     root      331.2K Mar 15 02:12 hello
$ docker images | grep hello
hello        v1        b3762d8a6c76   12 minutes ago   5.92MB

为什么是 int 类型

code

package main

import (
	"fmt"
)

func main() {
	v := 4
	fmt.Printf("%T\n", v)

	f := ((v / 5.0) * 100.0) * 11.0 / 100.0
	fmt.Printf("Type of result %T for value %0.2f\n", f, f)

	fmt.Printf("\n%T\n", 4)
	f2 := ((4 / 5.0) * 100.0) * 11.0 / 100.0
	fmt.Printf("Type of result %T for value %0.2f\n", f2, f2)
}

输出结果:

int
Type of result int for value %!f(int=00)

int
Type of result float64 for value 8.80

GO SSA

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

列表为空,暂无数据

关于作者

0 文章
0 评论
0 人气
更多

推荐作者

淹不死的鱼

文章 0 评论 0

zhangMack

文章 0 评论 0

爱的故事

文章 0 评论 0

linces

文章 0 评论 0

早乙女

文章 0 评论 0

鸵鸟症

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击“接受”或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。