GO cookbook 食用指南

716 阅读5分钟

go 编译部署

goreleaser: goreleaser.com/install/

使用 GoReleaser 发布你的应用

协程并发数控制

方案一:使用sync.WaitGroup 和 chan 控制协程并发数

  jobCount := 10
	// sync.WaitGroup 监控所有协程的状态,从而保证主协程结束时所有的子协程已经退出
	group := sync.WaitGroup{}
	
    // 定一个桶 桶的容量为2,桶满了阻塞,类似令牌桶。保证每次同时运行的gorutine数量不会超过桶容量,达到每次并发控制gorutine数量, 使用struct{} 不占内存
	buckets := make(chan struct{},2)
	for i:=0;i < jobCount;i++ {
		buckets <- struct{}{}
		group.Add(1)
		go func(i int) {
			fmt.Println("task ",i)
			time.Sleep(time.Second) // 刻意睡 1 秒钟,模拟耗时
			//fmt.Printf("index: %d,goroutine Num: %d \n", i, runtime.NumGoroutine())
			<- buckets
			group.Done()
		}(i)
		fmt.Printf("index: %d,goroutine Num: %d \n", i, runtime.NumGoroutine())
	}
	group.Wait()

方案二: 不使用 sync 实现并发控制, 并发50个,使用atomic.AddInt32 原子增加,达到任务总数,往done chan里填充一个元素,取消阻塞,任务结束

func main() {
	chMax := make(chan struct{}, 50)

	done := make(chan struct{}, 1)
	var dataTotal int32 = 0
	for i := 0; i < 100; i++ {
		chMax <- struct{}{}
		go func(index int) {
			fmt.Println(index)
			atomic.AddInt32(&dataTotal, 1)
			<-chMax
			if dataTotal == 100 {
				done <- struct{}{}
			}
		}(i)
	}
	<-done
	fmt.Println(">>", dataTotal)
}

多worker消费

	jobCount := 10
	group := sync.WaitGroup{}

	// chan容量为3
	jobsChan := make(chan int,5)

	// 起3个worker
	workerCount := 3
	for w:=0; w <= workerCount; w++ {
		go func(w int){
			for j := range jobsChan {
				fmt.Printf("worker %d get chan msg %d \n",w,j)
				time.Sleep(time.Second)
				group.Done()
			}
		}(w)
	}

	// 发布消息到chan
	for j :=0; j < jobCount; j++ {
		jobsChan <- j
		group.Add(1)
		fmt.Printf("index: %d,goroutine Num: %d\n", j, runtime.NumGoroutine())
	}

	group.Wait()

信号监听服务优雅关闭

func main()  {
	ch := make(chan os.Signal, 1)
	cxt, cancel := context.WithCancel(context.Background())
	i := 0
	// 协成服务
	go func(cxt context.Context) {
	    // 服务主体...
		Loop:
		for {
			select {
			case <-cxt.Done():
				log.Println("结束服务")
				break Loop
			case <-time.After(3 * time.Second):
				i++
				log.Println(i, "...")
			}
		}
	}(cxt)
	signal.Notify(ch, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
    // 监听信号
	if s, ok := <-ch; ok {
		log.Println(ok, s, "关闭服务")
		cancel()
		time.Sleep(time.Second * 5)
	}
}

go 实用库

simplejson 复杂json解析

gin-swagger: 生成swaggerapi文档

go 项目结构参考

github.com/bilibili/sn…

二进制读写

c.biancheng.net/view/4570.h…

package main
import (
	"bytes"
	"encoding/binary"
	"fmt"
)

func main() {
	by := []byte{0x00, 0x00, 0x03, 0xe8} //{0x00, 0x00, 0x03, 0xe8}
	var num int32
	# byte中为16进制数
	bytetoint(by, &num)
	fmt.Println(int(num), num)

	# 测试 int -> byte
	# int 为十进制数字ascii码 32~126 为ASCII可显示字符,如35对应的显示字符为'#'
	by2 := []byte{}
	var num2 int32
	num2 = 35
	by2 = inttobyte(&num2)
	fmt.Println(by2)

	// 测试 byte -> int
	var num3 int32
	bytetoint(by2, &num3)
	fmt.Println(num3)
}

// byte 转化 int
func bytetoint(by []byte, num *int32) {
	b_buf := bytes.NewBuffer(by)
	binary.Read(b_buf, binary.BigEndian, num)
}

// 数字 转化 byte
func inttobyte(num *int32) []byte {
	b_buf := new(bytes.Buffer)
	binary.Write(b_buf, binary.BigEndian, num)
	return b_buf.Bytes()
}

进制换算

func DecConvertToX(n, num int) (string, error) {
	if n < 0 {
		return strconv.Itoa(n), errors.New("只支持正整数")
	}
	if num != 2 && num != 8 && num != 16 {
		return strconv.Itoa(n), errors.New("只支持二、八、十六进制的转换")
	}
	result := ""
	h := map[int]string{
		0:  "0",
		1:  "1",
		2:  "2",
		3:  "3",
		4:  "4",
		5:  "5",
		6:  "6",
		7:  "7",
		8:  "8",
		9:  "9",
		10: "A",
		11: "B",
		12: "C",
		13: "D",
		14: "E",
		15: "F",
	}
	for ; n > 0; n /= num {
		lsb := h[n%num]
		result = lsb + result
	}
	return result, nil
}

DecConvertToX(26, 2) # 转2进制 => 11010
DecConvertToX(26, 8) # 转8进制 => 32
DecConvertToX(26, 16) # 转16进制 => 0x1A

性能优化

www.tsingson.io/tech/go-per…

go 命令


压缩go build的二进制文件,可使用 go build -ldflags "-s -w" -o go_init main.go
 -s    disable symbol table
 -w    disable DWARF generation

这样处理可以删除掉调试符号,从而显著减小了文件大小(平均20%),也可以相对的隐藏一些源码信息。

如果你觉得这样压缩之后文件还是比较大,那么我们还可以再加一个UPX壳,这样编译过后的二进制文件

-mod=vendor 以vendor目录中的包作为依赖源

还可以压缩到原文件大小的五分之一。具体操作如下:
upx安装:https://github.com/upx/upxgo build 之后生成的二进制文件如go_init, 执行
upx go_init

编译MacOS可执行文件:
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o go_init main.go

编译windows下可执行文件:
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o go_init main.go

atomic 原子操作

package main

import (
	"fmt"
	"sync/atomic"
	"time"
)
var ops uint64

for i := 0; i < 10000; i++ {
	go func() {
		atomic.AddUint64(&ops, 1)
	}()
}

time.Sleep(time.Second)
opsFinal := atomic.LoadUint64(&ops)
fmt.Println("ops:", opsFinal)

go 锁

不要在锁里面执行费时操作。 这里 “锁里面” 是指在mutex.Lock和mutex.Unlock之间的代码

在锁的最佳编程实践中,如果明确一组数据的并发访问符合 “绝大部分情况下是读操作,少量情况有写操作” ,这种 “读多写少” 特征,那么应该用读写锁。

读操作

mutex.RLock()
defer mutex.RUnlock()
doReadOnlyThings

如果是锁里面是写操作,代码就和普通锁一样,如下:

mutex.Lock()
defer mutex.Unlock()
doWriteThings

读操作不阻止读操作,阻止写操作; 写操作阻止一切,不管读操作还是写操作。

sync.Pool

Pool用于存储那些被分配了但是没有被使用,而未来可能会使用的值,以减小垃圾回收的压力,避免重复创建 对比

// 一个[]byte的对象池,每个对象为一个[]byte
var bytePool = sync.Pool{
  New: func() interface{} {
    b := make([]byte, 1024)
    return &b
  },
}

func main() {
  a := time.Now().Unix()
  // 不使用对象池
  for i := 0; i < 1000000000; i++{
    obj := make([]byte,1024)
    _ = obj
  }
  b := time.Now().Unix()
  // 使用对象池
  for i := 0; i < 1000000000; i++{
    obj := bytePool.Get().(*[]byte)
    _ = obj
    bytePool.Put(obj)
  }
  c := time.Now().Unix()
  fmt.Println("without pool ", b - a, "s")
  fmt.Println("with    pool ", c - b, "s")
}
// without pool  34 s
// with    pool  24 s

container/list 链表

package main

import (
    "container/list"
    "fmt"
)

func main() {
    nums := list.New()
    nums.PushBack(1)    // 追加新元素到末尾,返回该元素指针
    // 添加元素到开头
    nums.PushFront("Cynhard")
    for e := nums.Front(); e != nil; e = e.Next() {
        fmt.Println(e.Value)
    }
}
// 追加另一个列表到末尾
func (l *List) PushBackList(other *List)
// 添加新元素到开头,返回该元素指针
func (l *List) PushFront(v interface{}) *Element
// 添加另一个列表到开头
func (l *List) PushFrontList(other *List)
// 在mark后面插入新元素,返回新元素指针
func (l *List) InsertAfter(v interface{}, mark *Element) *Element
// 在mark前插入新元素,返回新元素指针
func (l *List) InsertBefore(v interface{}, mark *Element) *Element

context.WithCancel 父协成关闭,同时关闭子协成

func someHandler() {
    ctx, cancel := context.WithCancel(context.Background())
    go doStuff(ctx)
 
//10秒后取消doStuff
    time.Sleep(10 * time.Second)
    cancel()
 
}
 
//每1秒work一下,同时会判断ctx是否被取消了,如果是就退出
func doStuff(ctx context.Context) {
    for {
        time.Sleep(1 * time.Second)
        select {
        case <-ctx.Done():
            logg.Printf("done")
            return
        default:
            logg.Printf("work")
        }
    }
}
func main() {
    logg = log.New(os.Stdout, "", log.Ltime)
    someHandler()
    logg.Printf("down")
}

go 测试

前置条件:

  • 1、文件名须以"_test.go"结尾

  • 2、方法名须以"Test"打头,并且形参为 (t *testing.T)

测试整个文件:$ go test -v hello_test.go

测试单个函数:$ go test -v hello_test.go -test.run TestHello

测试push下的某个方法: go test -v ./push -run TestGetAccessToken

压力测试

# hello_test.go

func Benchmark_Division(b *testing.B) {
    for i := 0; i < b.N; i++ { //use b.N for looping
        Division(4, 5)
    }
}

func Benchmark_TimeConsumingFunction(b *testing.B) {
    b.StopTimer() //调用该函数停止压力测试的时间计数

    //做一些初始化的工作,例如读取文件数据,数据库连接之类的,
    //这样这些时间不影响我们测试函数本身的性能

    b.StartTimer() //重新开始时间

    b.N=1234 //自定义执行1234次

    for i := 0; i < b.N; i++ {
        Division(4, 5)
    }
}

执行如下命令go test -v ./hello_test.go ./division.go -bench="."只显示压力测试,因为hello_test.go有依赖division.go所以需要放到一起编译执行 其中-bench="."表示执行所有压力测试函数

$ go test -v ./webbench_test.go ./division.go  -bench=".*"
goos: linux
goarch: amd64
Benchmark_Division-8                    2000000000               0.74 ns/op
Benchmark_TimeConsumingFunction-8           1234                 0.96 ns/op
PASS
ok      command-line-arguments  1.558s

上面信息说明Benchmark_Division默认执行了2000000000次,而Benchmark_TimeConsumingFunction通过设置 b.N=1234 执行不同次数;每次的执行平均时间分别是0.74纳秒和0.96纳秒,总运行时间1.558秒

go 生态包

分布式跟踪:

https://www.jaegertracing.io/docs/1.13/
https://github.com/opentracing/opentracing-go

Go RSA

blog.csdn.net/qq_37133717…

Golang time/rate 限速器

go 学习

reading.developerlearning.cn alexstocks.github.io/html/about.…

高级编程 www.yuque.com/ksco/uiondt…

优秀博客

draveness.me/

go 编译

Go 语言编写移动端 Android 和 iOS SDK