References
Install
下载安装
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.2.linux-amd64.tar.gz
添加环境变量
export GOPATH=/data/username/go # 修改 GOPATH,默认是 $HOME/go
export GO111MODULE=on # 默认开启 go module
export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin # 添加环境变量
查看环境变量
go env
Go Modules
开启go modules
go env -w GO111MODULE="auto"
# 如果无法访问proxy.golang.org,需要修改proxy
go env -w GOPROXY=https://goproxy.cn,direct
# 公司proxy:https://goproxy.woa.com
export GOPROXY="https://yizhenchen:vm4UIuF1@goproxy.woa.com,direct"
export GOPRIVATE=""
export GOSUMDB="sum.woa.com+643d7a06+Ac5f5VOC4N8NUXdmhbm8pZSXIWfhek5JSmWdWrq7pLX4"
export no_proxy=$no_proxy",goproxy.woa.com"
使用
# 初始化包
go mod init example/user/hello
# build and install
go install example/user/hello
# 自动检测依赖包
go mod tidy
配置境内源
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
Go Tools
# 帮助文档
go help run|get|mod
Godoc
godoc -http=:6060
visit http://{ip}:6060/pkg/{package-name} to view documents
Build
编译
go build -v
交叉编译
env GOOS=linux GOARCH=arm64 go build -v
Run
运行
go run main.go
指定核数运行
GOMAXPROCS=1 ./your_program
或者在代码中设置
package main
import (
"runtime"
)
func main() {
runtime.GOMAXPROCS(1)
}
即使设置了 GOMAXPROCS 为 1,操作系统的调度器仍然可能在不同的核心之间迁移程序的执行线程
要严格控制程序只在一个特定的 CPU 核心上运行,需要设置进程的 CPU 亲和性(CPU affinity)
# 将程序绑定到 CPU 0 上
taskset -c 0 ./your_program
Test
假设待测代码被放在 hello.go
文件中
package main
import "fmt"
func Hello(name string) string {
return "Hello, " + name
}
func main() {
s := Hello("world")
fmt.Println(s)
}
新建一个测试文件,以 _test.go
结尾,即 hello_test.go
对要测试的函数前面加上 Test
前缀,参数传入 *testing.T
package main
import "testing"
func TestHello(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
if got != want {
t.Errorf("got '%q' want '%q'", got, want)
}
}
在一个 Test
函数中,可以通过 t.Run()
创建多个子测试
func TestHello(t *testing.T) {
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
if got != want {
t.Errorf("got '%q' want '%q'", got, want)
}
})
t.Run("say hello world when an empty string is supplied", func(t *testing.T) {
got := Hello("")
want := "Hello, World"
if got != want {
t.Errorf("got '%q' want '%q'", got, want)
}
})
}
通过 t.Helper()
创建辅助函数,这里将断言重构为函数
func TestHello(t *testing.T) {
assertCorrectMessage := func(t *testing.T, got, want string) {
t.Helper()
if got != want {
t.Errorf("got '%q' want '%q'", got, want)
}
}
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
assertCorrectMessage(t, got, want)
})
t.Run("empty string defaults to 'world'", func(t *testing.T) {
got := Hello("")
want := "Hello, World"
assertCorrectMessage(t, got, want)
})
}
添加示例,这些示例在运行测试时会检查输出是否与 // Output
中的值一致。示例会被展示到自动文档中
func ExampleAdd() {
sum := Add(1, 5)
fmt.Println(sum)
// Output: 6
}
由于数组不能直接比较,如果测试需要比较数组可以用反射
func TestSumAllTails(t *testing.T) {
checkSums := func(t *testing.T, got, want []int) {
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
t.Run("make the sums of tails of", func(t *testing.T) {
got := SumAllTails([]int{1, 2}, []int{0, 9})
want := []int{2, 9}
checkSums(t, got, want)
})
t.Run("safely sum empty slices", func(t *testing.T) {
got := SumAllTails([]int{}, []int{3, 4, 5})
want := []int{0, 9}
checkSums(t, got, want)
})
}
运行测试
帮助文档
# help
go help test
假设项目结构如下
myproject/
├── main.go
└── mypackage/
├── mypackage.go
└── mypackage_test.go
运行测试
# 运行项目所有测试
go test .
# 运行某个子 package 中所有测试
go test ./mypackage
# 运行 mypackage 目录下所有包含 `TestMyFunction` 名称的 Test 函数
go test -run=TestMyFunction ./mypackage
计算测试覆盖率
go test -cover
Benchmark
编写基准测试(benchmarks),代码会运行 b.N
次,并测量需要多长时间
func BenchmarkRepeat(b *testing.B) {
for i := 0; i < b.N; i++ {
Repeat("a")
}
}
运行基准测试
# 运行所有 Benchmark 函数
go test -bench=.
# 运行所有名称包含 Repeat 的 Benchmark 函数
go test -bench=Repeat
# run benchmarks with memory statistics
go test -bench=. -benchmem
# output `64 B/op` means each operation (on average) allocated 64 bytes
# output `2 allocs/op` means there were 2 memory allocations per operation
# 指定 benchmark 运行时间
go test -bench=. -benchtime=5s
# 指定 CPU 数量运行
go test -bench=. -cpu=1,2,4 # 分别以 1、2、4 核 CPU 运行基准测试
How to read benchmark results:
Architecture
Project structure
├── adapter // Adapter层,适配各种框架及协议的接入,比如:Gin,tRPC,Echo,Fiber 等
├── application // App层,处理Adapter层适配过后与框架、协议等无关的业务逻辑
│ ├── consumer //(可选)处理外部消息,比如来自消息队列的事件消费
│ ├── dto // App层的数据传输对象,外层到达App层的数据,从App层出发到外层的数据都通过DTO传播
│ ├── executor // 处理请求,包括command和query
│ └── scheduler //(可选)处理定时任务,比如Cron格式的定时Job
├── domain // Domain层,最核心最纯粹的业务实体及其规则的抽象定义
│ ├── gateway // 领域网关,model的核心逻辑以Interface形式在此定义,交由Infra层去实现
│ └── model // 领域模型实体
├── infrastructure // Infra层,各种外部依赖,组件的衔接,以及domain/gateway的具体实现
│ ├── cache //(可选)内层所需缓存的实现,可以是Redis,Memcached等
│ ├── client //(可选)各种中间件client的初始化
│ ├── config // 配置实现
│ ├── database //(可选)内层所需持久化的实现,可以是MySQL,MongoDB,Neo4j等
│ ├── distlock //(可选)内层所需分布式锁的实现,可以基于Redis,ZooKeeper,etcd等
│ ├── log // 日志实现,在此接入第三方日志库,避免对内层的污染
│ ├── mq //(可选)内层所需消息队列的实现,可以是Kafka,RabbitMQ,Pulsar等
│ ├── node //(可选)服务节点一致性协调控制实现,可以基于ZooKeeper,etcd等
│ └── rpc //(可选)广义上第三方服务的访问实现,可以通过HTTP,gRPC,tRPC等
└── pkg // 各层可共享的公共组件代码
Code snippets
string
修改字符串中的一个字符
str := "hello"
c := []byte(str)
c[0] = 'c'
s2 := string(c) // s2 == "cello"
获取字符串的子串
substr := str[n:m]
遍历一个字符串
// gives only the bytes:
for i:=0; i<len(str); i++ {
fmt.Println(str[i])
}
// gives the Unicode characters:
for index, unicodeChar := range str {
fmt.Println(index, unicodeChar) //
}
获取一个字符串的字节数
len(str)
获取一个字符串的字符数
len([]rune(str))
// or
utf8.RuneCountInString(str)
连接字符串
// 使用 `+=`
str1 := "Hello "
str2 := "World!"
str1 += str2 //str1 == "Hello World!"
// 使用 `bytes.Buffer`,当字符串数目特别多时推荐使用这种方式,而非 `+=`
var buffer bytes.Buffer
buffer.WriteString(str1)
buffer.WriteString(str2)
s := buffer.String()
// 使用`strings.Join()`
s := strings.Join([]string{str1, str2}, " ")
array/slice
创建
arr1 := new([len]type)
slice1 := make([]type, len)
初始化
arr1 := [...]type{i1, i2, i3, i4, i5}
arrKeyValue := [len]type{i1: val1, i2: val2}
var slice1 []type = arr1[start:end]
切片的最后一个元素
line = line[:len(line)-1]
遍历一个数组/切片
for i:=0; i < len(arr); i++ {
fmt.Println(arr[i])
}
for ix, value := range arr {
fmt.Println(value)
}
复制切片,注意创建目标切片时容量一定要大于源切片,否则无法复制
source := []int{1, 2, 3}
target := make([]int, len(source))
copy(target, source)
map
创建
map1 := make(map[keytype]valuetype)
初始化
map1 := map[string]int{"one": 1, "two": 2}
遍历
for key, value := range map1 {
fmt.Println(key, value)
}
检测键是否存在
val1, ok = map1[key1]
if val1, ok = map1[key1]; ok {
fmt.Println(val1)
}
删除一个键
delete(map1, key1)
struct
创建
type Person struct {
Name string
Age int
}
p := new(Person)
初始化
p := &Person{"Chris", 10}
// or
p := &Person{
Name: "Chris",
Age: 10,
}
通常情况下,为每个结构体定义一个构建函数,并推荐使用构建函数初始化结构体
func NewPerson(name string, age int) *Person {
return &Person{name, age}
}
p := NewPerson("Chris", 10)
interface
检测一个值 v
是否实现了接口 Stringer
if v, ok := v.(Stringer); ok {
fmt.Printf("implements String(): %s\n", v.String())
}
使用接口实现类型分类
switch x.(type) {
case bool:
fmt.Printf("param #%d is a bool\n", i)
case float64:
fmt.Printf("param #%d is a float64\n", i)
case int, int64:
fmt.Printf("param #%d is an int\n", i)
case nil:
fmt.Printf("param #%d is nil\n", i)
case string:
fmt.Printf("param #%d is a string\n", i)
default:
fmt.Printf("param #%d’s type is unknown\n", i)
}
function
闭包
func() {
fmt.Println("I'm a closure")
}()
捕捉 panic
func protect(g func()) {
defer func() {
log.Println("done")
// Println executes normally even if there is a panic
if x := recover(); x != nil {
log.Printf("run time panic: %v", x)
}
}()
log.Println("start")
g()
}
goroutine
遍历一个通道
for v := range ch {
// do something with v
}
阻塞主程序直到协程完成
ch := make(chan int) // Allocate a channel.
// Start something in a goroutine; when it completes, signal on the channel.
go func() {
// doSomething
ch <- 1 // Send a signal; value does not matter.
}()
doSomethingElseForAWhile()
<-ch // Wait for goroutine to finish; discard sent value.
通道的工厂模板
func pump() chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
}()
return ch
}
通道迭代器模式
func (c *container) Iter () <- chan item {
ch := make(chan item)
go func () {
for i:= 0; i < c.Len(); i++{ // or use a for-range loop
ch <- c.items[i]
}
} ()
return ch
}
限制同时处理的请求数
package main
const MAXREQS = 50
var sem = make(chan int, MAXREQS)
type Request struct {
a, b int
replyc chan int
}
func process(r *Request) {
// do something
}
func handle(r *Request) {
sem <- 1 // doesn't matter what we put in it
process(r)
<-sem // one empty place in the buffer: the next request can start
}
func server(service chan *Request) {
for {
request := <-service
go handle(request)
}
}
func main() {
service := make(chan *Request)
go server(service)
}
在多核CPU上实现并行计算
func DoAll(){
sem := make(chan int, NCPU) // Buffering optional but sensible
for i := 0; i < NCPU; i++ {
go DoPart(sem)
}
// Drain the channel sem, waiting for NCPU tasks to complete
for i := 0; i < NCPU; i++ {
<-sem // wait for one task to complete
}
// All done.
}
func DoPart(sem chan int) {
// do the part of the computation
sem <-1 // signal that this piece is done
}
func main() {
runtime.GOMAXPROCS(NCPU) // runtime.GOMAXPROCS = NCPU
DoAll()
}
终止一个协程
runtime.Goexit()
通知协程退出
func worker(exitChan chan bool) {
for {
select {
case <-exitChan:
fmt.Println("Worker exiting.")
return
default:
fmt.Println("Working...")
time.Sleep(1 * time.Second)
}
}
}
func RunCancel() {
exitChan := make(chan bool)
go worker(exitChan)
time.Sleep(3 * time.Second) // 模拟做了一些工作
exitChan <- true // 通知协程退出
time.Sleep(1 * time.Second) // 给协程时间退出
}
使用 context 通知协程退出
func workerWithCtx(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Worker exiting.")
return
default:
fmt.Println("Working...")
time.Sleep(1 * time.Second)
}
}
}
func RunCancelWithCtx() {
ctx, cancel := context.WithCancel(context.Background())
go workerWithCtx(ctx)
time.Sleep(3 * time.Second) // 模拟做了一些工作
cancel() // 通知协程退出
time.Sleep(1 * time.Second) // 给协程时间退出
}
使用输入通道和输出通道代替锁
func Worker(in, out chan *Task) {
for {
t := <-in
process(t)
out <- t
}
}
超时模板
timeout := make(chan bool, 1)
go func() {
time.Sleep(1e9) // one second
timeout <- true
}()
select {
case <- ch:
// a read from ch has occurred
case <- timeout:
// the read from ch has timed out
}
取消耗时很长的同步调用
ch := make(chan error, 1)
go func() { ch <- client.Call("Service.Method", args, &reply) } ()
select {
case resp := <-ch
// use resp and reply
case <- time.After(timeoutNs):
// call timed out
break
}
// time.After 会创建一个 Timer,在触发前不会被 GC 回收
// 如果会在很多个协程启用,不能这样写,可能会有内存泄露
delay := time.NewTimer(3 * time.Minute)
defer delay.Stop()
for {
delay.Reset(3 * time.Minute)
select {
case resp := <-ch:
// ...
case <- delay.C:
// time out
}
}
file
打开一个文件并读取
file, err := os.Open("input.dat")
if err != nil {
fmt.Printf("An error occurred on opening the inputfile\n" +
"Does the file exist?\n" +
"Have you got acces to it?\n")
return
}
defer file.Close()
iReader := bufio.NewReader(file)
for {
str, err := iReader.ReadString('\n')
if err != nil {
return // error or EOF
}
fmt.Printf("The input was: %s", str)
}
}
exit
在程序出错时终止程序
if err != nil {
fmt.Printf("Program stopping with error %v", err)
os.Exit(1)
}
// or
if err != nil {
panic("ERROR occurred: " + err.Error())
}
Debug
go get
问题
不能安装github包
因为go get是基于git的方式获取仓库的,然后默认用的是https的,被拒绝了,我们需要换成ssh的
git config --global url.git@github.com:.insteadOf https://github.com/
证书过期
# 加上 -insecure 参数,deprecated
go get -insecure https://git.code.oa.com/tpstelemetry/cgroups
# 设置 GOINSECURE 环境变量
export GOINSECURE="git.code.oa.com/*"
需要输入帐密
go get disables the “terminal prompt” by default. This can be changed by setting an environment variable of git:
env GIT_TERMINAL_PROMPT=1 go mod tidy
找不到外部包(<1.11)
example.go:3:8: no required module provides package XXX: go.mod file not found in current directory or any parent directory; see 'go help modules'
go get ./...
- 要把项目建到
$GOPATH/src/
下,否则会报go get: no install location for directory XXX outside GOPATH
Don’t split the main
package
不然编译器会报错
查看运行的Go程序
go get -u github.com/google/gops
gops
VSCode自动删除未引用包
在settings.json
中加上
"[go]": {
"editor.codeActionsOnSave": {
"source.organizeImports": false
}
}