go 编译部署
goreleaser: goreleaser.com/install/
协程并发数控制
方案一:使用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 项目结构参考
二进制读写
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
性能优化
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/upx
在go 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
Golang time/rate 限速器
go 学习
reading.developerlearning.cn alexstocks.github.io/html/about.…
高级编程 www.yuque.com/ksco/uiondt…