├── .gitignore ├── README.md ├── assets └── custom.js ├── book.toml ├── src ├── SUMMARY.md ├── about.md ├── cgo │ ├── cgo入门.md │ ├── cgo的使用场景.md │ ├── cgo简介.md │ ├── 动态链接和静态链接.md │ └── 指针和内存互访.md ├── example_code.md ├── go-runtime │ ├── gc.md │ ├── go-channel.md │ ├── go-map.md │ ├── runtime.md │ ├── scheduler.md │ ├── 内存分配器 .md │ └── 内存分配器.md ├── go-socket编程 │ ├── go-socks5实现.md │ ├── go-tcp-udp服务.md │ ├── go-websocket.md │ ├── http2协议详解.md │ ├── http3协议解析.md │ ├── http协议详解.md │ ├── socket.md │ └── 深入理解connection-multiplexing.md ├── go1.18%E6%B3%9B%E5%9E%8B.md ├── go1.18workspace.md ├── go1.18泛型.md ├── go内嵌kv数据库 │ ├── README.md │ ├── badger.md │ ├── boltdb.md │ └── goleveldb.md ├── go分布式系统 │ ├── 2pc.md │ ├── etcd.md │ ├── gossip.md │ ├── raft.md │ ├── 全局时间戳.md │ └── 分布式系统.md ├── go和数据库 │ ├── go使用es.md │ ├── go使用mongodb.md │ ├── go使用mysql.md │ └── go使用redis.md ├── go实战 │ ├── go_http2开发.md │ ├── go程序物理内存占用高的问题.md │ └── go语言结构体优雅初始化.md ├── go并发编程 │ ├── errgroup.md │ ├── goroutine同步.md │ ├── semaphore.md │ ├── singleflight.md │ ├── sync_map.md │ ├── sync_once.md │ ├── sync_pool.md │ ├── 使用channel做goroutine同步.md │ ├── 原子操作.md │ ├── 并发和并行.md │ └── 条件变量.md ├── go开发环境搭建 │ ├── helloworld.md │ ├── 环境搭建.md │ └── 集成开发工具.md ├── go标准库 │ ├── README.md │ ├── bytes.md │ ├── flag.md │ ├── fmt.md │ ├── go标准库概述.md │ ├── json.md │ ├── log.md │ ├── sort.md │ ├── strconv.md │ ├── strings.md │ ├── time.md │ ├── 文件读写.md │ └── 标准库概述.md ├── go汇编 │ ├── go汇编使用场景.md │ ├── go汇编基础.md │ ├── go汇编笔记.md │ ├── go汇编简介.md │ ├── go汇编运用.md │ └── 函数.md ├── go泛型对传统orm的影响.md ├── go语言基础 │ ├── README.md │ ├── goroutine和channel.md │ ├── go语言基础.md │ ├── interface.md │ ├── map.md │ ├── package和可见性.md │ ├── panic和recover.md │ ├── string.md │ ├── 函数.md │ ├── 反射.md │ ├── 变量.md │ ├── 基本类型.md │ ├── 常量.md │ ├── 指针类型.md │ ├── 数组和切片.md │ ├── 流程控制.md │ ├── 结构体和方法.md │ ├── 运算.md │ └── 错误处理.md ├── go调试 │ ├── GODEBUG追踪gc.md │ ├── GODEBUG追踪调度器.md │ └── go调试器.md ├── go项目管理 │ ├── go-modules.md │ ├── go-test.md │ ├── go命令.md │ └── go项目管理.md ├── img │ ├── 1.png │ ├── 2.png │ ├── 2pc1.png │ ├── 2pc2.png │ ├── CAP.png │ ├── asm1.png │ ├── concurreat.jpg │ ├── docker-swarm1.png │ ├── docker-swarm2.png │ ├── docker-swarm3.png │ ├── docker-swarm4.png │ ├── docker1.png │ ├── docker2.png │ ├── docker3.png │ ├── go_learningmap.png │ ├── godebug-diagram1.png │ ├── godebug-diagram2.png │ ├── godebug-diagram3.png │ ├── godebug-diagram4.png │ ├── godebug-diagram5.png │ ├── http2-1.svg │ ├── http2-2.svg │ ├── http2-3.svg │ ├── http2-4.svg │ ├── ide1.png │ ├── ide2.png │ ├── ide3.png │ ├── ide4.png │ ├── map1.png │ ├── map2.png │ ├── map3.png │ ├── profile-1.png │ ├── profile-2.png │ ├── raft-1.png │ ├── raft-2.png │ ├── raft-3.png │ ├── raft-4.gif │ ├── raft-4.png │ ├── raft-5.gif │ ├── raft-6.gif │ ├── raft-7.gif │ ├── scheduler-1.png │ ├── wechat.png │ ├── wechatgroup.png │ ├── yamux1.png │ └── yamux2.png ├── profiling │ ├── profiling.md │ ├── 内存分析实战.md │ └── 火焰图.md ├── web服务 │ ├── Go-web服务.md │ ├── go-web服务.md │ ├── http参数获取和http-client.md │ ├── http请求参数校验.md │ └── web服务框架.md ├── 一文让你体验算法之美.md ├── 微服务 │ ├── Go-micro微服务框架.md │ ├── docker.md │ ├── go-micro微服务框架.md │ ├── grpc和protobuf.md │ ├── 容器编排.md │ └── 微服务.md └── 绪论.md └── theme ├── index.hbs └── style3.css /.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LEARNING-GO 2 | 3 | 一本面向Go语言学习者的开源教程。 4 | 5 | ## 关于本书 6 | 7 | 本书旨在帮助读者从零开始学习Go语言,循序渐进地掌握Go语言的核心概念和实践技能。适合: 8 | - Go语言初学者 9 | - 想要深入理解Go语言特性的开发者 10 | - 正在转向Go语言的其他语言开发者 11 | 12 | [在线阅读](https://learninggo.986532.vip) 13 | 14 | ## 内容大纲 15 | 16 | - 第一章:Go语言基础 17 | - 环境搭建 18 | - 基本语法 19 | - 数据类型 20 | - 控制结构 21 | - 第二章:函数与方法 22 | - 第三章:结构体与接口 23 | - 第四章:并发编程 24 | - 第五章:标准库使用 25 | - 第六章:项目实战 26 | 27 | ## 本地开发 28 | 29 | 本书籍使用[mdbook](https://rust-lang.github.io/mdBook/index.html)构建。 30 | 31 | ### 启动开发服务器 32 | ```bash 33 | mdbook serve 34 | ``` 35 | 36 | 执行mdbook serve指令后,打开浏览器,输入:http://localhost:3000/ 即可看到渲染页面 37 | 38 | # build 39 | `mdbook build` 40 | 41 | ## 联系我 42 | ![微信](https://learninggo.986532.vip/img/wechat.png) 43 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["wida"] 3 | language = "zh" 4 | multilingual = false 5 | src = "src" 6 | title = "Go一站式编程" 7 | 8 | [output.html] 9 | additional-css = ["theme/style3.css"] 10 | additional-js = ["assets/custom.js"] 11 | git-repository-url = "https://github.com/widaT/learning-go" 12 | edit-url-template = "https://github.com/widaT/learning-go/edit/master/{path}" 13 | 14 | [output.html.playground] 15 | editable = true 16 | copy-js = true 17 | # line-numbers = true 18 | 19 | [output.html.fold] 20 | enable = true 21 | level = 1 22 | 23 | [rust] 24 | edition = "2021" #在线运行用2021版本的 -------------------------------------------------------------------------------- /src/about.md: -------------------------------------------------------------------------------- 1 | # 关于本书籍 2 | 3 | 本书籍归纳和总结我本人的一些经验。一些文章资料参考互联网相关内容。 4 | 5 | # 关于我 6 | 7 | 一个混迹在魔都10多年的资深后端程序员,专注后端高性能,高并发编程。主要涉及编程语言有golang和rust。 8 | 9 | ## 联系我 10 | - Github: https://github.com/widaT 11 | - Gitee: https://gitee.com/wida 12 | - mail: wida59@gmail.com 13 | - 微信: 14 | ![微信](https://learninggo.bjxw.xyz/img/wechat.png) 15 | - Go语言学习群:如果过期可加我微信,拉你入群 16 | ![微信群](https://learninggo.bjxw.xyz/img/wechatgroup.png) 17 | - Discord:[![](https://badgen.net/discord/members/xy79m8kCT7)](https://discord.gg/xy79m8kCT7) -------------------------------------------------------------------------------- /src/cgo/cgo的使用场景.md: -------------------------------------------------------------------------------- 1 | # cgo的使用场景 2 | 3 | 在本章的开头,我们建议大家非必要情况不要用cgo。本小节我们详细来看什么时候用cgo。 4 | 5 | 6 | ## 场景1——提升算法效率 7 | 8 | 本例中要求计算512维的向量的欧式距离。我们用go原生实现了算法,然后使用c/c++平台的avx(Advanced Vector Extensions 高级向量拓展集)实现同样的算法,同时比对下效率。 9 | ```go 10 | package main 11 | /* 12 | #cgo CFLAGS: -mavx -std=c99 13 | #include //AVX: -mavx 14 | float avx_euclidean_distance(const size_t n, float *x, float *y) 15 | { 16 | __m256 vsub,vsum={0},v1,v2; 17 | for(size_t i=0; i < n; i=i+8) { 18 | v1 = _mm256_loadu_ps(x+i); 19 | v2 = _mm256_loadu_ps(y+i); 20 | vsub = _mm256_sub_ps(v1, v2); 21 | vsum = _mm256_add_ps(vsum, _mm256_mul_ps(vsub, vsub)); 22 | } 23 | __attribute__((aligned(32))) float t[8] = {0}; 24 | _mm256_store_ps(t, vsum); 25 | return t[0] + t[1] + t[2] + t[3] + t[4] + t[5] + t[6] + t[7]; 26 | } 27 | */ 28 | import "C" 29 | 30 | import ( 31 | "fmt" 32 | "time" 33 | ) 34 | 35 | func euclideanDistance(size int,x, y []float32) float32 { //cgo实现欧式距离 36 | dot := C.avx_euclidean_distance((C.size_t)(size), (*C.float)(&x[0]), (*C.float)(&y[0])) 37 | return float32(dot) 38 | } 39 | func euclidean(infoA, infoB []float32) float32 { //go原生实现欧式距离 40 | var distance float32 41 | for i, number := range infoA { 42 | a := number - infoB[i] 43 | distance += a * a 44 | } 45 | return distance 46 | } 47 | func main() { 48 | size := 512 49 | x := make([]float32, size) 50 | y := make([]float32, size) 51 | for i := 0; i < size; i++ { 52 | x[i] = float32(i) 53 | y[i] = float32(i + 1) 54 | } 55 | 56 | stime := time.Now() 57 | times := 1000 58 | for i:=0;i 30 | #include //C.free 依赖这个头文件 31 | 32 | void myprint(char* s) { 33 | printf("%s\n", s); 34 | } 35 | */ 36 | import "C" 37 | 38 | import "unsafe" 39 | 40 | func main() { 41 | cs := C.CString("Hello from stdio\n") 42 | C.myprint(cs) 43 | C.free(unsafe.Pointer(cs)) 44 | } 45 | ``` 46 | 47 | ```bash 48 | $ go run main.go 49 | Hello from stdio 50 | ```` 51 | 52 | ## 参考资料 53 | 54 | [cgo-is-not-go](https://dave.cheney.net/2016/01/18/cgo-is-not-go) -------------------------------------------------------------------------------- /src/cgo/指针和内存互访.md: -------------------------------------------------------------------------------- 1 | # 指针和内存互访 2 | 3 | 指针是c语言的灵魂,同样指针在go语言中也有很重要的位置。在cgo中go的指针和c的指针可以互相传递,但大多时候这种传递是危险的。go语言是一种有GC的语言,你很难知道指针指向的内容是否已经被回收。而c语言的指针通常情况下是稳定的(除非手动释放,或者返回局部变量指针)。本小节我们来探讨下cgo中指针的运用,以及cgo中go和c内存互相访问。 4 | 5 | 6 | ## 指针 7 | 8 | 在本节中,术语Go指针表示指向Go分配的内存的指针(例如通过使用&运算符或调用go内置函数`new`), 9 | 术语C指针表示指向C分配的内存的指针(例如通过调用`C.malloc`)。 10 | 指针是指向内存的起始地址,以指针类型是无关的。在c语言中我们使用`void*`类型无关的指针,而在go是`unsafe.Pointer`。这两者指针在cgo中是可以互相传递的。 11 | 12 | | c | go | 13 | | ------------- |:-------------:| 14 | |void*|unsafe.Pointer| 15 | 16 | 17 | ## go访问c的内存 18 | 19 | go的由于内存策略的原因,很多时候向系统申请的内存在使用完之后,不会立即返回给系统,而是留着给下次使用。如果有场景想立即返回给出给系统,可以用cgo实现。 20 | 21 | ```go 22 | package main 23 | 24 | /* 25 | #include 26 | 27 | void* m_malloc(int typeSize ,size_t length) { 28 | return malloc(typeSize * length); 29 | } 30 | */ 31 | import "C" 32 | import ( 33 | "fmt" 34 | "unsafe" 35 | ) 36 | 37 | func main() { 38 | c := (*[3]int32)(C.m_malloc(C.int(4),3)) 39 | defer C.free(unsafe.Pointer(c)) 40 | c[0],c[1],c[2] = 0,1,2 41 | fmt.Println(c,len(c)) 42 | } 43 | ``` 44 | 45 | ```bash 46 | $ go run main.go 47 | &[0 1 2] 3 48 | ``` 49 | 50 | ## c访问go内存 51 | 52 | 我们[cgo入门](./cgo入门.md)介绍了cgo字符串和字节数组的转换,用了`C.CString`的方法将 go string转成 `* C.char`。我们同时强调了在使用完`* C.char`后记得调用`C.free` 方法。我们来看下`C.CString`的定义 53 | 54 | ```go 55 | const cStringDef = ` 56 | func _Cfunc_CString(s string) *_Ctype_char { 57 | p := _cgo_cmalloc(uint64(len(s)+1)) 58 | pp := (*[1<<30]byte)(p) 59 | copy(pp[:], s) 60 | pp[len(s)] = 0 61 | return (*_Ctype_char)(p) 62 | } 63 | ` 64 | ``` 65 | 可以看出`C.CString`是使用cgo函数`_cgo_cmalloc`申请了内存,然后把go string数据拷贝到这块内存上。所以这块内存在c函数使用是安全的。但是很明显,耗性能,浪费内存空间。 66 | 67 | cgo在调用c函数时,能保证作为参数传入c函数的go内存不会发生移动。所以我们可以使用如下方法让c函数临时访问go内存。 68 | 69 | ```go 70 | package main 71 | 72 | /* 73 | #include 74 | #include 75 | int mconcat(const char * a,const char *b,char *c){ 76 | return sprintf(c,"%s%s",a,b); 77 | } 78 | */ 79 | import "C" 80 | import ( 81 | "fmt" 82 | "reflect" 83 | "unsafe" 84 | ) 85 | 86 | func main() { 87 | a := C.CString("hello") 88 | b := C.CString("world") 89 | 90 | defer func() { 91 | C.free(unsafe.Pointer(a)) //需要手动释放 92 | C.free(unsafe.Pointer(b)) //需要手动释放 93 | }() 94 | 95 | ret := make([]byte,20) 96 | p := (*reflect.SliceHeader)(unsafe.Pointer(&ret)) 97 | 98 | len := C.mconcat(a,b,(*C.char)(unsafe.Pointer(p.Data))) 99 | fmt.Println(len,string(ret[:len])) 100 | } 101 | ``` 102 | 103 | ```bash 104 | $ go run main.go 105 | 10 helloworld 106 | ``` 107 | 108 | 上面的例子中我们使用了go语言的`[]byte`去接收c函数字符串拼接,这样子省去很多额外的内存分配。 109 | 110 | ## 参考资料 111 | 112 | - [cgo](https://golang.org/cmd/cgo/) -------------------------------------------------------------------------------- /src/example_code.md: -------------------------------------------------------------------------------- 1 | # demo 代码 2 | 3 | [传送门](https://github.com/widaT/leaning-go-code/) -------------------------------------------------------------------------------- /src/go-runtime/gc.md: -------------------------------------------------------------------------------- 1 | # go的垃圾回收器 2 | 3 | 4 | # 参考资料 5 | 6 | [Golang源码探索(三) GC的实现原理](https://www.cnblogs.com/zkweb/p/7880099.html) -------------------------------------------------------------------------------- /src/go-runtime/go-channel.md: -------------------------------------------------------------------------------- 1 | # go channal是如何实现的 2 | 3 | 我们先通过golang汇编追踪下 make(chan,int)到底做了什么。 4 | 5 | ```golang 6 | package main 7 | import "fmt" 8 | func main() { 9 | ch := make(chan int,1) 10 | go func() { 11 | for { 12 | ch <-1 13 | } 14 | }() 15 | ret := <- ch 16 | fmt.Println(ret) 17 | } 18 | ``` 19 | 20 | ```bash 21 | go build --gcflags="-S" main.go 22 | ``` 23 | 24 | 看到如下这段go汇编代码 25 | ``` 26 | LEAQ type.chan int(SB), AX //获取type.chan int 类型指针 27 | MOVQ AX, (SP) //压栈 make的第一个参数 28 | MOVQ $1, 8(SP) //压栈 make的第一个参数 29 | CALL runtime.makechan(SB) //实际调用 runtime.makechan函数 30 | ``` 31 | 32 | 那么`ch := make(chan int,1)` 其实是调用 runtime.makechan的方法, 33 | 34 | go 1.13 源码 35 | 36 | ```golang 37 | func makechan(t *chantype, size int) *hchan { 38 | elem := t.elem 39 | if elem.size >= 1<<16 { 40 | throw("makechan: invalid channel element type") 41 | } 42 | if hchanSize%maxAlign != 0 || elem.align > maxAlign { 43 | throw("makechan: bad alignment") 44 | } 45 | mem, overflow := math.MulUintptr(elem.size, uintptr(size)) 46 | if overflow || mem > maxAlloc-hchanSize || size < 0 { 47 | panic(plainError("makechan: size out of range")) 48 | } 49 | var c *hchan 50 | switch { 51 | case mem == 0: 52 | c = (*hchan)(mallocgc(hchanSize, nil, true)) 53 | c.buf = c.raceaddr() 54 | case elem.ptrdata == 0: 55 | c = (*hchan)(mallocgc(hchanSize+mem, nil, true)) 56 | c.buf = add(unsafe.Pointer(c), hchanSize) 57 | default: 58 | c = new(hchan) 59 | c.buf = mallocgc(mem, elemblockevent, true) 60 | } 61 | c.elemsize = uint16(elem.size) 62 | c.elemtype = elem 63 | c.dataqsiz = uint(size) 64 | return c 65 | } 66 | ``` 67 | 68 | 返回的类型是*hchan。所以chan是一个指针类型,这样子chan在各个goroutine的传递 69 | 都是直接传chan,传递chan 指针。 70 | 71 | 我看从go源码的(src/runtime/chan.go)看到的hchan struct 72 | ```golang 73 | type hchan struct { 74 | qcount uint // 当前使用的个数 75 | dataqsiz uint // 缓冲区大小 make的第二个参数 76 | buf unsafe.Pointer // 缓存数组的指针,带缓冲区的chan指向缓冲区,无缓存的channel指向自己指针(仅做数据竞争分析使用) 77 | elemsize uint16 // 元素size 78 | closed uint32 //是否已经关闭 1已经关闭 0还没关闭 79 | elemtype *_type // 元素类型 80 | sendx uint // 发送的索引 比如缓冲区大小为3 这个索引 经历 0-1-2 然后再从0开始 81 | recvx uint // 接收的索引 82 | recvq waitq // 等待recv的goroutine链表 83 | sendq waitq // 等待send的goroutine链表 84 | lock mutex //互斥锁 85 | } 86 | ``` 87 | 88 | `ret := <- ch` 这个语句对于汇编是 89 | ```bash 90 | LEAQ ""..autotmp_12+64(SP), CX 91 | MOVQ CX, 8(SP) 92 | CALL runtime.chanrecv2(SB) 93 | ``` 94 | 95 | 我们看到源码中用 `chanrecv1` 和 `chanrecv2` 两个函数: 96 | ```golang 97 | // entry points for <- c from compiled code 98 | //go:nosplit 99 | func chanrecv1(c *hchan, elem unsafe.Pointer) { 100 | chanrecv(c, elem, true) 101 | } 102 | 103 | //go:nosplit 104 | func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) { 105 | _, received = chanrecv(c, elem, true) 106 | return 107 | } 108 | ``` 109 | 我们这边对应的是 `chanrecv2`, 那么什么时候是`chanrecv1`?什么时候是`chanrecv2`呢? 110 | 当 `<- ch` chan左边没有接收值的时候使用的是 `chanrecv1`,当左边有接收值的时候是`chanrecv2`,注意 chan 的接收值有两个 111 | ```go 112 | ret := <- ch 113 | //带bool值 114 | ret,ok := <- ch 115 | ``` 116 | 这两个都是使用`chanrecv2`。 117 | 118 | 119 | 120 | `ch <-1`对应的汇编是 121 | ``` 122 | LEAQ ""..stmp_0(SB), CX 123 | MOVQ CX, 8(SP) 124 | CALL runtime.chansend1(SB) 125 | ``` 126 | 这边可以看到实际上是调用`runtime.chansend1` 源码如下 127 | ```golang 128 | func chansend1(c *hchan, elem unsafe.Pointer) { 129 | chansend(c, elem, true, getcallerpc()) 130 | } 131 | ``` 132 | 实现上是调用`runtime.chansend` 133 | 134 | 135 | 136 | ## 获取chan的关闭状态 137 | 138 | 通过什么的一些源码阅读我们来实现下chan关闭状态的获取。 139 | 140 | ```go 141 | package main 142 | 143 | import ( 144 | "fmt" 145 | "unsafe" 146 | ) 147 | 148 | func main() { 149 | c := make(chan int, 10) 150 | fmt.Println(isClosed(&c)) 151 | close(c) 152 | fmt.Println(isClosed(&c)) 153 | } 154 | 155 | type hchan struct { 156 | qcount uint // total data in the queue 157 | dataqsiz uint // size of the circular queue 158 | buf unsafe.Pointer // points to an array of dataqsiz elements 159 | elemsize uint16 160 | closed uint32 161 | } 162 | 163 | func isClosed(c *chan int) uint32 { 164 | a := unsafe.Pointer(c) 165 | d := (**hchan)(a) 166 | return (*d).closed 167 | } 168 | ``` 169 | 170 | ```bash 171 | # go run main.go 172 | 0 173 | 1 174 | ``` 175 | 176 | # 参考文档 177 | [深度解密Go语言之channel](https://www.cnblogs.com/qcrao-2018/p/11220651.html#%E6%8E%A5%E6%94%B6) 178 | [understanding channels](https://speakerd.s3.amazonaws.com/presentations/10ac0b1d76a6463aa98ad6a9dec917a7/GopherCon_v10.0.pdf) -------------------------------------------------------------------------------- /src/go-runtime/runtime.md: -------------------------------------------------------------------------------- 1 | # go runtime简介 2 | 3 | 有别于java和c#的runtime是一个虚拟机,go的runtime和我们的go代码一同编译成二进制可执行文件。 4 | 5 | go的runtime负责: 6 | 7 | - goroutine的调度 8 | - go内存分配 9 | - 垃圾回收(GC) 10 | - 封装了操作系统底层操作,如syscall,原子操作,CGO 11 | - map,channel,slice,string内置类型的实现 12 | - 反射(reflection)的实现 13 | - pprof,trace,race的实现 14 | 15 | 本小节将依次介绍 16 | 17 | - map底层实现 18 | - channel的底层实现 19 | - goroutine调度器 20 | - go 内存分配器 21 | - go 垃圾会收器 -------------------------------------------------------------------------------- /src/go-runtime/scheduler.md: -------------------------------------------------------------------------------- 1 | # go的调度器 2 | 3 | 4 | ## GMP调度模型 5 | 6 | - G表示Goroutine; 7 | - M表示一个操作系统的线程; 8 | - P表示一个CPU处理器,通常P的数量等于CPU核数(GOMAXPROCS)。 9 | 10 | ![](../img/scheduler-1.png) 11 | 12 | 如上图所示 13 | 14 | - 全局队列的作用是负载均衡(Balance)。 15 | - M需要和P绑定后不停的执行G的任务。 16 | - P本地有个任务队列,可以无锁状态下执行高效操作。当本地队列为空时会尝试去全局队列去获取G,如果全局队列也为空,这个时候从其他有G的P哪里偷取一半G过来,放到自己的P本地队列。 17 | - M和P并不一定是一一对应的,通常P数量是等于GOMAXPROCS,M的数量则由调度器的监控线程决定的。 18 | 19 | 我们再看下《Go 1.5 源码剖析 》中的示意图 20 | ``` 21 | +-------------------- sysmon ---------------//-------+ 22 | | | 23 | | | 24 | +---+ +---+-------+ +--------+ +---+---+ 25 | go func() ---> | G | ---> | P | local | <=== balance ===> | global | <--//--- | P | M | 26 | +---+ +---+-------+ +--------+ +---+---+ 27 | | | | 28 | | +---+ | | 29 | +----> | M | <--- findrunnable ---+--- steal <--//--+ 30 | +---+ 31 | | 1. 语句go func() 创建G 32 | | 2. 放入P本地队列或者平衡到全局队列 33 | +--- execute <----- schedule 3. 唤醒或者新建M执行任务 34 | | | 4. 进入调度循环 schedul 35 | | | 5. 竭力获取待执行 G 任务并执行 36 | +--> G.fn --> goexit ----+ 6. 清理现场,重新进入调度循环 37 | ``` 38 | 39 | ``` 40 | M 通过修改寄存器,将执⾏栈指向 G ⾃带栈内存,并在此空间内分配堆栈帧,执⾏任务函数。 41 | 当需要中途切换时,只要将相关寄存器值保存回 G 空间即可维持状态,任何 M 都可据此恢复执⾏。 42 | 线程仅负责执⾏,不再持有状态,这是并发任务跨线程调度,实现多路复⽤的根本所在。 43 | ``` 44 | 45 | ## 调试调度器 46 | 47 | 调试go调度器,我们有专门的小节来介绍[GODEBUG追踪调度器](../go调试/GODEBUG追踪调度器.md) 48 | 49 | 50 | ## 参考资料 51 | 52 | - [雨痕的golang源码剖析](https://github.com/qyuhen/book) 53 | - [Go调度器系列(3)图解调度原理](https://segmentfault.com/a/1190000018775901) -------------------------------------------------------------------------------- /src/go-runtime/内存分配器 .md: -------------------------------------------------------------------------------- 1 | # 内存分配器 2 | 3 | # 参考资料 4 | - [go-memory-management-and-allocation](https://medium.com/a-journey-with-go/go-memory-management-and-allocation-a7396d430f44) 5 | - [[译]Go:内存管理与内存分配](https://juejin.im/post/5ddcdc5df265da05c33fcad2) 6 | - [Go 内存分配器可视化指南](https://mp.weixin.qq.com/s/RYtc5oZ4CmQZouLIcsloDw) -------------------------------------------------------------------------------- /src/go-runtime/内存分配器.md: -------------------------------------------------------------------------------- 1 | # go内存分配器(未完) 2 | -------------------------------------------------------------------------------- /src/go-socket编程/http3协议解析.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/go-socket编程/http3协议解析.md -------------------------------------------------------------------------------- /src/go-socket编程/http协议详解.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/go-socket编程/http协议详解.md -------------------------------------------------------------------------------- /src/go-socket编程/socket.md: -------------------------------------------------------------------------------- 1 | # Socket(套接字)编程 2 | 3 | ## 什么是Socket 4 | 5 | Socket 是对 TCP/IP 协议族的一种封装,是应用层与TCP/IP协议族通信的中间软件抽象层。 6 | Socket 还可以认为是一种网络间不同计算机上的进程通信的一种方法,利用三元组(ip地址,协议,端口)就可以唯一标识网络中的进程,网络中的进程通信可以利用这个标志与其它进程进行交互。 7 | Socket 起源于 Unix ,Unix/Linux 基本哲学之一就是“一切皆文件”,都可以用“打开(open) –> 读写(write/read) –> 关闭(close)”模式来进行操作。因此 Socket 也被处理为一种特殊的文件。 8 | 9 | ## Socket常见类型 10 | 11 | ### Datagram sockets 12 | 13 | 无连接Socket,使用用户数据报协议(UDP)。在Datagram sockets上发送或接收的每个数据包都经过单独寻址和路由。数据报Socket无法保证顺序和可靠性,因此从一台机器或进程发送到另一台机器或进程的多个数据包可能以任何顺序到达或根本不到达。 14 | 15 | ### Stream sockets 16 | 17 | 面向连接的Socket,使用传输控制协议(TCP),流控制传输协议(SCTP)或数据报拥塞控制协议(DCCP)。Stream sockets提供无记录边界的有序且唯一的数据流,并具有定义明确的机制来创建和销毁连接以及检测错误。Stream sockets可靠地按顺序传输数据。在Internet上,Stream sockets通常在TCP之上实现,以便应用程序可以使用TCP/IP协议在任何网络上运行。 18 | 19 | 20 | ## C/S模式中的Sockets 21 | 22 | 提供应用程序服务的计算机进程称为服务器,并在启动时创建处于侦听状态的Socket。这些Socket正在等待来自客户端程序的连接。 23 | 24 | 通过为每个客户端创建子进程并在子进程与客户端之间建立TCP连接,TCP服务器可以同时为多个客户端提供服务。为每个连接创建唯一的专用Socket。当与远程Socket建立Socket到Socket的虚拟连接或虚拟电路(也称为TCP 会话)时,它们处于建立状态,从而提供双工字节流。 25 | 26 | 服务器可以使用相同的本地端口号和本地IP地址创建多个同时建立的TCPSocket,每个Socket都映射到其自己的服务器子进程,为客户端进程服务。由于远程Socket地址(客户端IP地址和/或端口号)不同,因此操作系统将它们视为不同的Socket。即因为它们具有不同的Socket对元组。 27 | 28 | UDP Socket无法处于已建立状态,因为UDP是无连接的。因此netstat不会显示UDP Socket的状态。UDP服务器不会为每个并发服务的客户端创建新的子进程,但是同一进程将通过同一Socket顺序处理来自所有远程客户端的传入数据包。这意味着UDP Socket不是由远程地址标识的,而是仅由本地地址标识的,尽管每个消息都具有关联的远程地址。 29 | 30 | 31 | ## 总结 32 | 33 | 本小节简要的介绍了Sockets的一些概念,介绍了Socket常见的两种类型。Socket编程技术涵盖的面和知识体现相当广泛,同样它的运用更是广泛,好不夸张的说互联网的技术都是基于Socket的。 34 | 35 | ## 参考资料 36 | 37 | [wikipedia](https://en.wikipedia.org/wiki/Network_socket) -------------------------------------------------------------------------------- /src/go1.18%E6%B3%9B%E5%9E%8B.md: -------------------------------------------------------------------------------- 1 | # go泛型 2 | -------------------------------------------------------------------------------- /src/go1.18workspace.md: -------------------------------------------------------------------------------- 1 | # go 1.18 workspace使用 2 | 3 | go语言对于模块化代码组织方式说实话,不是很理想。从最早被人诟病的`go path`方式,包括后来稍微有点现代语言模块化方式的`go modules`也槽点满满。 4 | 5 | 虽然不尽人意,但是go官方都是没有放弃继续改进模块化代码组织方式。这次go1.18又有了新的一个功能叫做 `go workspace`,中文翻译为go工作区。 6 | 7 | ## 初识 go workspace 8 | 9 | ### 什么需要 go workspace? 10 | 11 | 我看了下工作区的[提案](https://go.googlesource.com/proposal/+/master/design/45713-workspace.md),说了`go workspace`设计的初衷 12 | 13 | ``` 14 | 在一个大项目中依赖多个go mod项目,而我们需要同时修改go mod项目代码,在原先go mod设计中,依赖的项目是只读的。 15 | ``` 16 | 17 | `workspace`方式是对现有`go mod`方式的补充而非替换,`workspace`会非常方便的在`go`语言项目组加入本地依赖库。 18 | 19 | ## 使用 go workspace 20 | `go` 命令行增加了 `go work`来支持`go workspace`操作。 21 | 22 | ```bash 23 | $ go help work 24 | Go workspace provides access to operations on workspaces. 25 | ... 26 | Usage: 27 | 28 | go work [arguments] 29 | 30 | The commands are: 31 | 32 | edit edit go.work from tools or scripts 33 | init initialize workspace file 34 | sync sync workspace build list to modules 35 | use add modules to workspace file 36 | ... 37 | ``` 38 | 39 | 我们看到 `go work` 有四个子命令. 40 | 41 | 接下来我们创建几个子项目: 42 | ```bash 43 | $ go work init a b c 44 | go: creating workspace file: no go.mod file exists in directory a 45 | ``` 46 | 额,报错了。a,b,c 一定是`go mod`项目才能使用.我们创建a,b,c目录,同时添加go mod 47 | ```bash 48 | $ tree . 49 | . 50 | ├── a 51 | │   └── go.mod 52 | ├── b 53 | │   └── go.mod 54 | ├── c 55 |   └── go.mod 56 | 57 | ``` 58 | 59 | ```bash 60 | $ go work init a b c 61 | $ tree . 62 | . 63 | ├── a 64 | │   └── go.mod 65 | ├── b 66 | │   └── go.mod 67 | ├── c 68 | │   └── go.mod 69 | └── go.work 70 | ``` 71 | ok,工作区创建好了。我们规划下,a目录项目编译可执行文件,b,c为lib库。在工作区目录下`a,b,c 三个模块互相可见`。 72 | 73 | ```bash 74 | $ tree 75 | . 76 | ├── a 77 | │ ├── a.go 78 | │ └── go.mod 79 | ├── b 80 | │ ├── b.go 81 | │ └── go.mod 82 | ├── c 83 | │ ├── c.go 84 | │ └── go.mod 85 | └── go.work 86 | ``` 87 | 88 | b.go 89 | 90 | ```go 91 | import "fmt" 92 | 93 | func B() { 94 | fmt.Println("I'm mod b") 95 | } 96 | 97 | ``` 98 | 99 | c.go 100 | 101 | ```go 102 | package c 103 | 104 | import ( 105 | "b" 106 | "fmt" 107 | ) 108 | 109 | func C() { 110 | b.B() 111 | fmt.Println("I'm mod c") 112 | } 113 | 114 | ``` 115 | 116 | a.go 117 | 118 | ```go 119 | package main 120 | 121 | import ( 122 | "b" 123 | "c" 124 | ) 125 | func main() { 126 | b.B() 127 | c.C() 128 | } 129 | ``` 130 | 正如你所看到的,a 依赖 b,c依赖b。好了,我们run一下程序 131 | 132 | ```bash 133 | $ go run a/a.go 134 | I'm mod b 135 | I'm mod b 136 | I'm mod c 137 | $ cd a/ #切换到 a目录下看是不是可以 138 | $ go run a.go # a目录下 b,c也对a可见 139 | I'm mod b 140 | I'm mod b 141 | I'm mod c 142 | ``` 143 | 144 | ## 关于是否提交go.work文件 145 | 146 | 我们看到很多文章说不建议提交go.work文件,说实话我看到这个建议很奇怪,rust项目管理工具`cargo`也有类似的工作区概念,cargo的项目肯定会提交工作区文件,因为这个文件本身是项目的一部分。很多文章说`go.work`这个文件主要用于本地开发,我倒觉得未必有啊,一个多模块,大团队项目不是也可以以工作区项目开发吗?工作区可以非常清楚的划分功能模块,在一个代码仓库里头没啥问题吧。可能这一块会有很多争议,目前用的人少,等大规模运用了在看看情况。 147 | 148 | -------------------------------------------------------------------------------- /src/go1.18泛型.md: -------------------------------------------------------------------------------- 1 | # go 1.18泛型初体验 2 | 3 | go.1.18beta版发布,众所周知这个go.1.18版本会默认启用go泛型。这个版本也号称go最大的版本改动。 4 | 5 | ## 初识golang的泛型 6 | 7 | 我们写一个demo来看看go的泛型是长啥样 8 | 9 | ```go 10 | 11 | package main 12 | 13 | import ( 14 | "fmt" 15 | ) 16 | 17 | type OrderTypes interface { 18 | ~int | ~float32 | ~string 19 | } 20 | 21 | func max[T OrderTypes](x, y T) T { 22 | if x > y { 23 | return x 24 | } 25 | return y 26 | } 27 | 28 | func main() { 29 | fmt.Println(max(1, 11), max("abc", "eff")) 30 | } 31 | 32 | ``` 33 | ok run 一下代码 34 | ```bash 35 | $ go run main.go 36 | 11 eff 37 | ``` 38 | 39 | `~int | ~float32 | ~string`我们看到了新的语法,`~`是新的操作符,主要用来做类型约束使用, `~int`代表类型约束为`int`类型,`~int | ~float32 | ~string`则代表约束为 `int` 或者 `float32` 或者 `string`。上面额例子中,这三个类型刚好是可以比较的能进行 ">" 操作的。 40 | 41 | 当然上面的代码是演示用的,在真正的项目中我们应该使用标准`constraints`提供的`Ordered`来做约束。 42 | 43 | ```go 44 | import ( 45 | "constraints" 46 | ) 47 | func max[T constraints.Ordered](x, y T) T { 48 | if x > y { 49 | return x 50 | } 51 | return y 52 | } 53 | ``` 54 | 55 | `constraints`标准库定义了一下常用的类型约束,如`Ordered`,`Signed`,`Unsigned`,`Integer`,`Float`。 56 | 57 | 58 | 59 | ## 提高生产力的泛型 60 | 61 | 我们通过下面的例子来看看泛型,如何提高我们的生产力。我们将为所有`slice`类型添加三件套`map`,`reduce`,`filter` 62 | 63 | ```go 64 | func Map[Elem1, Elem2 any](s []Elem1, f func(Elem1) Elem2) []Elem2 { 65 | r := make([]Elem2, len(s)) 66 | for i, v := range s { 67 | r[i] = f(v) 68 | } 69 | return r 70 | } 71 | 72 | func Reduce[Elem1, Elem2 any](s []Elem1, initializer Elem2, f func(Elem2, Elem1) Elem2) Elem2 { 73 | r := initializer 74 | for _, v := range s { 75 | r = f(r, v) 76 | } 77 | return r 78 | } 79 | 80 | func Filter[Elem any](s []Elem, f func(Elem) bool) []Elem { 81 | var r []Elem 82 | for _, v := range s { 83 | if f(v) { 84 | r = append(r, v) 85 | } 86 | } 87 | return r 88 | } 89 | 90 | func Silce() { 91 | sliceA := []int{3, 99, 31, 63} 92 | //通过sliceA 生成sliceB 93 | sliceB := Map(sliceA, func(e int) float32 { 94 | return float32(e) + 1.3 95 | }) 96 | fmt.Println(sliceB) 97 | //找最大值 98 | max := Reduce(sliceB, 0.0, func(a, b float32) float32 { 99 | if a > b { 100 | return a 101 | } 102 | return b 103 | }) 104 | fmt.Println(max) 105 | //过滤sliceA中大于30的组成新的slice 106 | sliceC := Filter(sliceA, func(e int) bool { 107 | if e > 30 { 108 | return true 109 | } 110 | return false 111 | }) 112 | fmt.Println(sliceC) 113 | } 114 | 115 | func main() { 116 | Silce() 117 | } 118 | ``` 119 | 120 | ```bash 121 | $ go run main.go 122 | [4.3 100.3 32.3 64.3] 123 | 100.3 124 | [99 31 63] 125 | ``` 126 | 127 | ## 带泛型的`struct` 128 | 129 | 接下来我们看一下带泛型的`struct` 130 | 131 | ```go 132 | //定义的时候需要加约束 133 | type Student[T constraints.Unsigned] struct { 134 | Age T 135 | } 136 | 137 | //后续struct方法编写的时候 约束就不能写了 138 | func (s *Student[T]) GetAge() T { 139 | return s.Age 140 | } 141 | ``` 142 | 143 | 我们初始化带泛型的结构体 144 | 145 | ```go 146 | age := uint(3) 147 | s := &Student[uint]{Age: age} 148 | fmt.Println(s.GetAge()) //3 149 | s1 := &Student[uint16]{Age: uint16(age)} 150 | fmt.Println(s1.GetAge()) //3 151 | ``` 152 | 153 | ## 总结 154 | 155 | go的泛型目前还没有官方推荐的最佳实践,标准库的代码也基本没改成泛型。但总归走出支持泛型这一步,后续丰富标准库应该是后面版本的事情了。再看[go2](https://github.com/golang/go/blob/dev.go2go/src/cmd/go2go/testdata/go2path/src/)代码的时候发现一个有意思的东西--`orderedmap`。感兴趣的同学可以去看看。 -------------------------------------------------------------------------------- /src/go内嵌kv数据库/README.md: -------------------------------------------------------------------------------- 1 | # 为什么需要内嵌数据库 2 | 3 | go的生态中有好多内嵌的k/v数据库,为什么我们需要内嵌数据库? 4 | 5 | - 更高的效率:内嵌数据库因为和程序共享一个程序地址空间减少IPC开销,所以比独立数据库具有更高的性能 6 | - 更简洁的部署方案:因为内嵌了,所以就不需要额外部署单独的数据库,减少程序依赖. 7 | - 做单机存储引擎:一些优秀的内嵌数据库可以作为单机存储引擎,然后通过分布式一致性协议和分片策略可以集群成大型分布式数据库.例如etcd中使用boltDB,dgraph使用的badger. 8 | 9 | 说道内嵌型kv数据库,不得不提的就rocksdb了.这些年基于rocksdb单机存储引擎之上开发的分布式数据库数不胜数.例如tidb,cockroachdb等等.rocksdb是Facebook基于leveldb加强的kv存储引擎,用c++编写的,go需要通过cgo可以内嵌rocksdb,可以参考[gorocksdb](https://github.com/tecbot/gorocksdb)。但是go一般不用rocksdb作为内嵌kv数据库,首先rocksdb安装非常繁琐,二cgo的性能一直被诟病,而且容易造成内存泄露。 10 | 11 | 12 | 本小节依次介绍blotdb,goleveldb,badger三款表典型的内嵌数据库. 13 | 14 | -------------------------------------------------------------------------------- /src/go内嵌kv数据库/boltdb.md: -------------------------------------------------------------------------------- 1 | # [BoltDB](https://github.com/etcd-io/bbolt) 2 | 3 | 我们现在说的bolt一般都会说是etcd维护的bolt版本,老的bolt作者已经不再维护。bolt提供轻量级的kv数据库,内部是一个B+树结构体,支持完整的ACID事务。 4 | bolt是基于内存映射(mmap)的数据库,通常情况使用的内存会比较大,bolt适合kv数量不是特别大的场景。 5 | 6 | # 创建数据库 7 | 8 | ```go 9 | package main 10 | 11 | import ( 12 | "log" 13 | 14 | bolt "go.etcd.io/bbolt" 15 | ) 16 | 17 | func main() { 18 | // 打开my.db 如果文件不存在则会创建 19 | db, err := bolt.Open("my.db", 0600, nil) 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | defer db.Close() 24 | 25 | } 26 | ``` 27 | 28 | # 事务 29 | 30 | bolt支持只读事务和读写事务, 在同一个时间只能有一个读写事务,但是可以有多个只读事务。 31 | 32 | ## 只读事务使用 `db.View` 33 | 34 | ```go 35 | err := db.View(func(tx *bolt.Tx) error { 36 | ... 37 | return nil 38 | }) 39 | ``` 40 | 41 | ## 读写事务使用 `db.Update` 42 | 43 | ```go 44 | 45 | err := db.Update(func(tx *bolt.Tx) error { 46 | ... 47 | return nil 48 | }) 49 | ``` 50 | 51 | ## Buckets(桶) 52 | 53 | Bolt在内部用Buckets来组织相关的kv键值对,在同一个Bucket里头key值是不允许重复的,bolt中一个文件代表一个database,那么bucket就相当于一个table。 54 | 55 | 56 | ### 操作Bucket 57 | 58 | ```go 59 | db.Update(func(tx *bolt.Tx) error { 60 | b, err := tx.CreateBucket([]byte("MyBucket")) //创建bucket,通常会使用 CreateBucketIfNotExists 61 | if err != nil { 62 | return fmt.Errorf("create bucket: %s", err) 63 | } 64 | 65 | err := b.Put([]byte("answer"), []byte("42")) //写入kv 66 | v := b.Get([]byte("answer")) //获取value 67 | fmt.Printf("%s",v) 68 | return nil 69 | }) 70 | ``` 71 | 72 | ## key的遍历 73 | 74 | 在kv数据库中我们需要对key进行精心设计,无论在取值或者遍历的时候都需要快速的定位key的位置。在bolt中key是基于B树有序的。 75 | 一般有如下三种场景遍历key,遍历,范围遍历,前缀遍历 76 | 77 | ### 遍历桶中所有key 78 | 79 | ```go 80 | db.View(func(tx *bolt.Tx) error { 81 | b := tx.Bucket([]byte("MyBucket")) 82 | c := b.Cursor() 83 | for k, v := c.First(); k != nil; k, v = c.Next() { 84 | fmt.Printf("key=%s, value=%s\n", k, v) 85 | } 86 | return nil 87 | }) 88 | ``` 89 | 90 | 或者使用 `ForEach` 遍历所有桶下面所有key 91 | 92 | ```go 93 | db.View(func(tx *bolt.Tx) error { 94 | // Assume bucket exists and has keys 95 | b := tx.Bucket([]byte("MyBucket")) 96 | 97 | b.ForEach(func(k, v []byte) error { 98 | fmt.Printf("key=%s, value=%s\n", k, v) 99 | return nil 100 | }) 101 | return nil 102 | }) 103 | ``` 104 | 105 | ### key范围遍历 106 | 107 | ```go 108 | db.View(func(tx *bolt.Tx) error { 109 | c := tx.Bucket([]byte("Events")).Cursor() 110 | 111 | min := []byte("1990-01-01T00:00:00Z") 112 | max := []byte("2000-01-01T00:00:00Z") 113 | 114 | for k, v := c.Seek(min); k != nil && bytes.Compare(k, max) <= 0; k, v = c.Next() { 115 | fmt.Printf("%s: %s\n", k, v) 116 | } 117 | 118 | return nil 119 | }) 120 | ``` 121 | 122 | ### key前缀遍历 123 | 124 | ```go 125 | db.View(func(tx *bolt.Tx) error { 126 | c := tx.Bucket([]byte("MyBucket")).Cursor() 127 | prefix := []byte("1234") 128 | for k, v := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, v = c.Next() { 129 | fmt.Printf("key=%s, value=%s\n", k, v) 130 | } 131 | 132 | return nil 133 | }) 134 | ``` -------------------------------------------------------------------------------- /src/go内嵌kv数据库/goleveldb.md: -------------------------------------------------------------------------------- 1 | # Leveldb 2 | 3 | LevelDB是Google开源的由C++编写的基于LSM-Tree的KV数据库,有超高的随机写,顺序读/写性能,但是随机读的性能很一般,LevelDB一般应用在查询少写多的场景。 4 | 5 | 6 | ## LSM-Tree 7 | 8 | LSM Tree(Log-Structured Merge-Tree),是为了解决在内存不足,磁盘随机IO太慢下的写入性能问题。在LSM中增删改操作都是新增记录,新的记录会最早被索引到,这样才的操作会造成数据重复,后续的合并操作会消除这样的冗余。LSM通过这样的方式把随机IO变成顺序IO,大大提高写入性能。 9 | 10 | 了解LSM-Tree细节可以查看[LSM树原理探究](https://juejin.im/post/6844903863758094343)。 11 | 12 | 13 | ## goleveldb使用 14 | 15 | 16 | ### 创建db 17 | 18 | ```go 19 | db, err := leveldb.OpenFile("path/to/db", nil) 20 | defer db.Close() 21 | ``` 22 | 23 | ### 增删改 24 | 25 | ```go 26 | data, err := db.Get([]byte("key"), nil) 27 | ... 28 | err = db.Put([]byte("key"), []byte("value"), nil) 29 | ... 30 | err = db.Delete([]byte("key"), nil) 31 | ``` 32 | 33 | 34 | ### 迭代器 35 | 36 | 37 | #### 遍历所有 38 | 39 | ```go 40 | iter := db.NewIterator(nil, nil) 41 | for iter.Next() { 42 | key := iter.Key() 43 | value := iter.Value() 44 | ... 45 | } 46 | iter.Release() 47 | err = iter.Error() 48 | ``` 49 | 50 | #### 设置起始点遍历 51 | 52 | ```go 53 | iter := db.NewIterator(nil, nil) 54 | for ok := iter.Seek(key); ok; ok = iter.Next() { 55 | // Use key/value. 56 | ... 57 | } 58 | iter.Release() 59 | err = iter.Error() 60 | ``` 61 | 62 | #### 范围遍历 63 | 64 | ```go 65 | iter := db.NewIterator(&util.Range{Start: []byte("foo"), Limit: []byte("xoo")}, nil) 66 | for iter.Next() { 67 | // Use key/value. 68 | ... 69 | } 70 | iter.Release() 71 | err = iter.Error() 72 | ... 73 | ``` 74 | 75 | #### 前缀遍历 76 | 77 | ```go 78 | iter := db.NewIterator(util.BytesPrefix([]byte("foo-")), nil) 79 | for iter.Next() { 80 | // Use key/value. 81 | ... 82 | } 83 | iter.Release() 84 | err = iter.Error() 85 | ``` 86 | 87 | ### 批量操作 88 | 89 | ```go 90 | batch := new(leveldb.Batch) 91 | batch.Put([]byte("foo"), []byte("value")) 92 | batch.Put([]byte("bar"), []byte("another value")) 93 | batch.Delete([]byte("baz")) 94 | err = db.Write(batch, nil) 95 | ``` 96 | 97 | 98 | ### 使用布隆过滤器 99 | 100 | ``` 101 | 布隆过滤器使用极少空间代价(bitmap)来判断一个值是否在这个集合里,它使用多个hash函数对key计算然后映射到bitmap上的位置,如果这个位置的值为1,则代表这个值可能存在(注意是可能存在),如果这个位置值为0,代表这个key一定不存在这个集合里。如果哈希函数够多,我们判断是否存在的可信度就越高。当然某些情况下判断出来值存在的误判,在实际应用场景中我们无非耗费点资源去实际的kv数据库里头查下看否能拿到value。布隆过滤器能快速的知道key不存这样子可以减少大量的好资源操作。 102 | ``` 103 | 104 | ```go 105 | o := &opt.Options{ 106 | Filter: filter.NewBloomFilter(10), 107 | } 108 | db, err := leveldb.OpenFile("path/to/db", o) 109 | ... 110 | defer db.Close() 111 | ... 112 | ``` -------------------------------------------------------------------------------- /src/go分布式系统/2pc.md: -------------------------------------------------------------------------------- 1 | # 分布式一致性算法——2PC(二阶段提交) 2 | 3 | ## 二阶段提交算法(Two-phase Commit,即2PC) 4 | 5 | 二阶段提交参与者有两种角色:协调者(coordinator)和参与者(participants) 6 | 7 | 二阶段提交顾名思义有两个阶段: 8 | 9 | - 第一个阶段称为准备阶段,调者(coordinator)向所有参与者(participants)发送提议(propose)并分别收咨询他们的意见,并且收集它们的反馈(vote)。 10 | 11 | ![](../img/2pc1.png) 12 | 13 | - 第二个阶段称为提交阶段,协调者(coordinator)根据反馈情况决定是提交(commit)或者中止(abort)事务。 14 | 15 | ![](../img/2pc2.png) 16 | 17 | 阶段2收集到的已经只要有一个是`abort`的话,整个事务就中止,当所有的参与者都给的反馈是`commit`的时候事务才提交。 18 | 19 | ## 二阶段提交的问题 20 | 21 | 如我们上一个小节介绍的二阶段提交属于CA(一致性+可用性)系统,当有节点宕机的时候,二阶段提交加故障处理就会变得复杂。 22 | 23 | 二阶段提交的主要问题: 24 | 25 | - 同步阻塞问题,二阶段提交的vote过程是同步阻塞的。 26 | - 协调者单点问题 27 | - 在协调者和参与者都宕机的情况下,可能出现严重的阻塞问题。在第二阶段协调者和一个参与者同时宕机,而且这个参与者在宕机前已经做了操作(commit/abort),这个时候协调者恢复,由于不知道宕机参与者的状态,协调者没办法判断是提交还是中止,这个时候只能等宕机的参与者恢复。 28 | - 脑裂问题,如果在第二点只有部分参与者收到执行命令,那么将导致数据不一致。 29 | 30 | 31 | ## 三阶段提交算法(three-phase Commit,即3PC) 32 | 33 | 三阶段提交算法(Three-Phase Commit, 3PC)最关键要解决的就是 coordinator和参与者同时挂掉导致数据不一致的问题,所以3PC在 2PC 中又添加一个阶段,这样三阶段提交就有:`CanCommit`、`PreCommit` 和`DoCommit`三个阶段。 34 | 35 | 3PC虽然能解决部分2PC的问题,但是同样过多的交互会导致其他的问题,3PC在实际项目中很少使用。感兴趣的同学可以去了解下,这边不再赘述。 36 | 37 | -------------------------------------------------------------------------------- /src/go分布式系统/gossip.md: -------------------------------------------------------------------------------- 1 | # 分布式一致性算法——Gossip算法 2 | 3 | 前两节我们介绍了CA算法的典型——2PC,CP算法的典型——Raft,本小节要介绍AP算法的典型——Gossip算法。 4 | 5 | Gossip算法有很多别名,流言算法,流行病算法等,Gossip最早是在 1987 年发表在 ACM 上的论文 《Epidemic Algorithms for Replicated Database Maintenance》中被提出。后来因为Cassandra中运用名声大噪。近几年区块链盛行,比特币(Bitcoin)和超级记账本( Fabric)同样使用了Gossip。 6 | 7 | 我们看下Gossip的算法模型: 8 | 9 | 和Raft算法不同,Gossip算法中的节点没有Leader和Follower之分,它们都是对等的。 10 | 假定在一个有边界的网络中,每个状态机节点随机和其他状态机节点通讯,经过一段时间后在这个网络中的所有状态机节点的状态会达成一致。每个状态机节点都可能知道其他状态机节点或者只知道他邻近的节点,通信后他们的最终状态是一致的。从这个算法模型上看确实很想流言和流行病的传播模型。 11 | 12 | 我在举个实际例子来理解这个模型: 13 | 14 | 我们的每个状态机节点带有三元祖数据(key,value,version),我们能保证version是单向递增的。假设我们节点中有A,B,C,E,F五个节点构成环形结构,然后每个节点和相邻的节点每隔1秒通讯一次,把自己的数据(key,value,version)推个两个相邻节点,这两个相邻节点拿到数据后和自己的数据做比对,如果数据比自己新则更新自己的数据。这个模型最多2秒就能让整个集群数据收敛成一致。 15 | 16 | 上面的例子还有很多需要讨论的,第一每次同步的数据是全量还是增量,第二每次更新是节点主动推还是节点主动去拉,还会既有推也有拉。这两个问题会衍生初Gossip的类型和通讯方式。 17 | 18 | 19 | ## Gossip 类型 20 | 21 | Gossip 有两种类型: 22 | 23 | - Anti-Entropy(反熵):以固定的概率传播所有的数据,可以理解为全量比对。 24 | - Rumor-Mongering(谣言传播):仅传播新到达的数据,可以理解为增量比对。 25 | 26 | Anti-Entropy模式会让节点物理资源(网络和cpu)负担很重,Rumor-Mongering模式对节点资源负担相对较小,但是如何界定新数据变得比较困难,而且很难容错,无法保证一致性,所以反而Anti-Entropy有更大的价值,对于Anti-Entropy模式的优化研究更多。Anti-Entropy模式并不是真正的传送所有数据,而是变成如何追踪整个数据的变动,然后快速的找到数据的差异将差异数据传送。默克尔树(Merkle Tree)就是非常不错的一差异定位算法,有兴趣的可以去了解下。 27 | 28 | 29 | ## Gossip算法的通讯方式 30 | 31 | gossip Node A 和 Node B有三种通信方式: 32 | 33 | push: A节点将数据和版本号(key,value,version)推送给B节点,B节点更新A中比自己新的数据 34 | pull:A仅将(key,version)推送给B,B将本地比A新的数据(Key,value,version)推送给A,A更新本地 35 | push/pull:A仅将(key,version)推送给B,B将本地比A新的数据(Key,value,version)推送给A,A更新本地,A再将本地比B新的数据推送给B,B更新本地。 36 | 37 | 从上面的描述上看,push/pull的方式虽然通讯次数最多但是仅需要一个时间周期就能让A,B节点完全同步,Cassandra就是采用这个方式。 38 | 39 | 40 | ## Gossip算法的优点: 41 | 42 | - 扩展性:网络可以允许节点的任意增加和减少,新增加的节点的状态最终会与其他节点一致。 43 | - 容错性:网络中任何节点的宕机和重启都不会影响 Gossip 消息的传播,Gossip 算法具有天然的分布式系统容错特性。 44 | - 去中心化:Gossip 算法不要求任何中心节点,所有节点都是对等的,任何一个节点无需知道整个网络状况,只要网络是连通的,任意一个节点就可以把消息散播到全网。 45 | 46 | 47 | ## Gossip算法的缺点: 48 | 49 | - Gossip算法无法确定某个时刻所有状态机的状态是否一致。 50 | - Gossip算法由于要经常和自己的相关节点通讯,因此可能造成大量冗余的网络流量,甚至可能造成流量风暴。 51 | 52 | 53 | ## 总结 54 | 55 | Gossip算法从他的特性来说应该是一种非常妙的算法,在非强一致性要求的领域非常实用,去中心话,同时又有天然的拓展性,顺带天然的故障检测属性。 56 | 在go生态在gossip的实现比较多,比较出门的有hashicorp实现的[memberlist](https://github.com/hashicorp/memberlist)。 57 | 58 | ## 参考资料 59 | 60 | - [flowgossip](http://www.cs.cornell.edu/home/rvr/papers/flowgossip.pdf) -------------------------------------------------------------------------------- /src/go分布式系统/分布式系统.md: -------------------------------------------------------------------------------- 1 | # 分布式系统简介 2 | 3 | ## 什么是分布式系统 4 | 5 | 计算机系统有两大基础任务——存储和计算,分布式系统编程是用多台计算机解决像单台计算机一样处理存储和计算问题。而这些问题是不适合用单台计算机去解决。不适合不是说不行,如果你有无限资源而且不考成本,你可以购买或者找人那设计那种单体计算机。但是很少有人拥有无限的资源。因此我们必须在某些实际成本收益曲线上找到适合自己的位置。在较小规模上,升级硬件是不错选择,但是随着问题规模的增加,你将通过靠升级单个节点硬件来解决问题,或者成本变得过高,分布式系统将会是最好的选择。 6 | 7 | 分布式系统(distributed system)的目标可以归纳为可拓展性,可拓展性包含 8 | 9 | - 大小可伸缩性:添加更多节点将使系统线性更快​​;扩大数据集不应增加延迟 10 | - 地理可伸缩性:应该可以使用多个数据中心,以减少响应用户查询所花费的时间,同时以合理的方式处理跨数据中心的延迟。 11 | - 管理可伸缩性:添加更多节点不会增加系统的管理成本(例如,管理员与机器的比例)。 12 | 13 | 衡量可拓展性有四个关键的指标性能、延时、可用性和容错 14 | 15 | - 性能:它的特点是与时间和资源相比,计算机系统完成的有用工作量。 16 | - 延时:是事物开始和发生之间的时间。 17 | - 可用性:系统处于运行状态的时间比例。如果用户无法访问系统,则称该系统不可用。 18 | - 容错:以归结为,预估可能遇到的故障,然后设计可以容忍的系统或算法。 19 | 20 | 分布式系统受两个物理因素的约束: 21 | 22 | - 节点数(随所需的存储和计算能力而增加) 23 | - 节点之间的距离 24 | 25 | 由这两个物理因素带入如下三个问题 26 | 27 | - 独立节点数量的增加会增加系统发生故障的可能性(降低可用性并增加管理成本) 28 | - 独立节点数量的增加可能会增加节点之间通信的需求(随着规模的增加而降低性能) 29 | - 地理距离的增加会增加远端节点之间通信的延迟(延迟会带来一致性问题) 30 | 31 | 这三个问题是非常难完全解决的,CAP定理告诉我们完美的分布式系统是不存在的。 32 | 33 | ## CAP定理 34 | 35 | CAP定理最初是由计算机科学家Eric Brewer提出的一个猜想。它指出对于一个分布式计算系统来说,不可能同时满足以下三点: 36 | - 一致性(Consistency):所有节点同时看到相同的数据(每次请求都能拿到最新的数据)。 37 | - 可用性(Availability):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(每次请求都能得到响应,但是数据可能不是最新的)。 38 | - 分区容错性(Partition tolerance):尽管由于网络或者节点故障,但是整个服务仍能对外提供服务(是否允许有节点故障)。 39 | 40 | 根据定理,分布式系统只能满足三项中的两项而不可能满足全部三项。 41 | ![](../img/CAP.png) 42 | 43 | 如上图显示,具有所有三个属性的交叉部分是无法实现的。然后我们得到这三种两两结合的不同的系统类型: 44 | 45 | CA(一致性+可用性)。示例包括完全严格的仲裁协议,例如2PC(两阶段提交)。 46 | CP(一致性+分区容错性)。示例包括多数派仲裁协议,其中少数派分区不可用,例如Paxos,Raft算法。 47 | AP(可用性+分区容错性)。示例包括使用冲突解决方案的协议,例如Gossip协议。 48 | 49 | CA和CP系统设计均提供相同的一致性模型:高度一致性。唯一的区别是CA系统不能容忍任何节点故障。在CP系统中2n+1个节点中可以容忍n个节点故障。CA系统不支持分区,它们通常使用两阶段提交算法,并且在传统的分布式关系数据库中很常见。 50 | 51 | ## 总结 52 | 53 | 本小节简要介绍了分布式系统的概念,以及分布式系统的挑战。我们还介绍了分布式系统中非常出名的CAP定理,我们知道了AP,CA,CP系统的设计侧重点。本章节后续会介绍这三个系统的代表协议(算法)Gossip,2PC,Raft(Paxos可能更有名,但是产业界实现很少,Raft目前用得更多)。 54 | 55 | # 参考资料 56 | 57 | - [distsys](http://book.mixu.net/distsys/single-page.html) -------------------------------------------------------------------------------- /src/go和数据库/go使用es.md: -------------------------------------------------------------------------------- 1 | # go使用es 2 | 3 | elasticsearch有官方的golang驱动[go-elasticsearch](https://github.com/elastic/go-elasticsearch)这个项目比较新, 4 | 另外一个常用的是 [elastic](https://github.com/olivere/elastic),这两个驱动文档和demo都比较少。es的查询语法也相对复杂,很多查询方式去翻翻它们的test文件才能发现方式。本小节使用`elastic`做演示,注意不同elasticsearch版本对于不同的client版本,例如elasticsearch 5.5.3对应的client版本为`gopkg.in/olivere/elastic.v5`。如果这个对应关系错误,很可能程序会出错,这个在`https://github.com/olivere/elastic`的readme文档也有介绍。 5 | 本小节的demo主要基于 `elasticsearch 5.5.3`,client为`gopkg.in/olivere/elastic.v5`。 6 | 7 | ## go链接es 8 | ```golang 9 | var client *elastic.Client 10 | func init() { 11 | var err error 12 | client, err = elastic.NewClient(elastic.SetURL("http://localhost:9200")) 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | } 17 | ``` 18 | 19 | ## CURD 20 | 21 | 测试数据结构体 22 | ```golang 23 | type Item struct { 24 | Id int64 `json:"id"` 25 | Appid string `json:"appid"` 26 | AppBAutoId string `json:"app_b_auto_id"` 27 | } 28 | ``` 29 | 30 | ### 添加 31 | ```golang 32 | func add() { 33 | //add one 34 | item := Item{Id:int64(21),Appid:fmt.Sprintf("app_%d",21),AppBAutoId:fmt.Sprintf("app_%d",21+200)} 35 | put, err := client.Index(). 36 | Index("es_test"). 37 | Type("test"). 38 | Id("1"). //这个id也可以指定,不指定的话 es自动生成一个 39 | BodyJson(item). 40 | Do(context.Background()) 41 | if err != nil { 42 | // Handle error 43 | panic(err) 44 | } 45 | fmt.Println(put) 46 | //add many 47 | bulkRequest := client.Bulk() 48 | for i:=0;i<20;i++ { 49 | item := Item{Id:int64(i),Appid:fmt.Sprintf("app_%d",i),AppBAutoId:fmt.Sprintf("app_%d",i+200)} 50 | bulkRequest.Add(elastic.NewBulkIndexRequest().Index("es_test").Type("test").Doc(item)) 51 | } 52 | bulkRequest.Do(context.TODO()) 53 | } 54 | 55 | ``` 56 | ### 查找 57 | ```golang 58 | func find() { 59 | //find one 60 | get1, err := client.Get(). 61 | Index("es_test"). 62 | Type("test"). 63 | Id("1"). 64 | Do(context.Background()) 65 | if err != nil { 66 | // Handle error 67 | panic(err) 68 | } 69 | if get1.Found { 70 | fmt.Printf("Got document %s in version %d from index %s, type %s\n", get1.Id, get1.Version, get1.Index, get1.Type) 71 | } 72 | var ttyp Item 73 | json.Unmarshal(*get1.Source,&ttyp) 74 | fmt.Println("item",ttyp) 75 | 76 | //find many 77 | searchResult, err := client.Search(). 78 | Index("es_test"). Sort("id", true). 79 | Type("test").From(0).Size(100). 80 | Do(context.TODO()) 81 | if err != nil { 82 | panic(err) 83 | } 84 | if searchResult.Hits.TotalHits >0 { 85 | var ttyp Item 86 | for _, item := range searchResult.Each(reflect.TypeOf(ttyp)) { 87 | t := item.(Item) 88 | fmt.Println("item ",t) 89 | } 90 | 91 | } 92 | } 93 | ``` 94 | ### 更新 95 | ```golang 96 | func update() { 97 | fmt.Println(client.Update().Index("es_test").Type("test").Id("1"). 98 | Doc(map[string]interface{}{"appid": "app_23"}).Do(context.TODO())) 99 | } 100 | ``` 101 | ### 删除 102 | ```golang 103 | func delete() { 104 | fmt.Println(client.Delete().Index("es_test").Type("test").Id("1").Do(context.TODO())) 105 | } 106 | 107 | ``` 108 | ## 统计查询 109 | ```golang 110 | func agg() { 111 | //获取最大的id 112 | searchResult, err := client.Search(). 113 | Index("es_test").Type("test"). 114 | Aggregation("max_id", elastic.NewMaxAggregation().Field("id")).Size(0).Do(context.TODO()) 115 | if err != nil { 116 | panic(err) 117 | } 118 | var a map[string]float32 119 | if searchResult != nil { 120 | if v, found := searchResult.Aggregations["max_id"]; found { 121 | json.Unmarshal([]byte(*v), &a) 122 | fmt.Println(a) 123 | } 124 | } 125 | //统计id相同的文档数 126 | searchResult, err = client.Search(). 127 | Index("es_test").Type("test"). 128 | Aggregation("count", elastic.NewTermsAggregation().Field("id")).Size(0).Do(context.TODO()) 129 | if err != nil { 130 | panic(err) 131 | } 132 | if searchResult != nil { 133 | if v, found := searchResult.Aggregations["count"]; found { 134 | var ar elastic.AggregationBucketKeyItems 135 | err := json.Unmarshal(*v, &ar) 136 | if err != nil { 137 | fmt.Printf("Unmarshal failed: %v\n", err) 138 | return 139 | } 140 | 141 | for _, item := range ar.Buckets { 142 | fmt.Printf("id :%v: count :%v\n", item.Key, item.DocCount) 143 | 144 | } 145 | } 146 | } 147 | } 148 | ``` 149 | # 参考资料 150 | - [olivere/elastic](https://github.com/olivere/elastic) 151 | - [elastic/wiki](https://github.com/olivere/elastic/wiki) 152 | - [QueryDSL](https://github.com/olivere/elastic/wiki/QueryDSL) -------------------------------------------------------------------------------- /src/go和数据库/go使用mongodb.md: -------------------------------------------------------------------------------- 1 | # go使用mongodb 2 | mongodb的go语言驱动比较流行是[mongo-go-driver](https://github.com/mongodb/mongo-go-driver)和[mgo](https://github.com/go-mgo/mgo),前者是mongodb官方出的go驱动,mgo之前一直是个人在维护的,后来作者由于个原因已经放弃维护,后面出现[mgo](https://github.com/globalsign/mgo)的分支还在继续维护不过更新频率还是比较低。本文主要介绍`mongo-go-driver`这个mongodb官方维护的版本,目前`mongo-go-driver`的版本已经是1.1.2已经可以用在生产环境。 3 | 4 | ## mongodb client 5 | 目前大多数golang数据库驱动client 都会用连接池的方式来运行。 `mongo-go-driver`也不例外。 6 | 连接MongoDB代码如下: 7 | ```golang 8 | client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017")) 9 | if err != nil { 10 | log.Fatal(err) 11 | } 12 | err = client.Ping(context.TODO(), nil) 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | fmt.Println("Connected to MongoDB!") 17 | ``` 18 | 运行下main.go程序 19 | ```bash 20 | # go run main.go 21 | Connected to MongoDB! 22 | ``` 23 | 这边需要注意下,采用连接池后一般很少自己close client而是继续放在链接池中举行供其他请求使用。 24 | 如果你确定已经不需要使用Client,可以使用如下代码收到关闭。 25 | ```golang 26 | err = client.Disconnect(nil) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | fmt.Println("Connection to MongoDB closed.") 31 | ``` 32 | 33 | ## 获取集合handle 34 | ```golang 35 | collection = client.Database("testing").Collection("students") 36 | ``` 37 | 38 | ## 为collection创建索引 39 | ```golang 40 | indexView := collection.Indexes() 41 | ret,err :=indexView.CreateOne(context.Background(), mongo.IndexModel{ 42 | Keys: bsonx.Doc{{"name", bsonx.Int32(-1)}}, 43 | Options: options.Index().SetName("testname").SetUnique(true), //这边设置了唯一限定,不设定默认不是唯一的 44 | }) 45 | fmt.Println(ret,err) 46 | ``` 47 | 48 | ## CURD操作 49 | 50 | ### 添加 51 | ```golang 52 | func add() { 53 | wida := Student{"wida", 32, "8001"} 54 | amy := Student{"amy", 25, "8002"} 55 | sunny := Student{"sunny", 35, "8003"} 56 | 57 | ret, err := collection.InsertOne(nil, wida) 58 | if err != nil { 59 | log.Fatal(err) 60 | } 61 | 62 | fmt.Println("写入一个文档", ret.InsertedID) 63 | student := []interface{}{amy, sunny} 64 | 65 | ret2, err := collection.InsertMany(nil, student) 66 | if err != nil { 67 | log.Fatal(err) 68 | } 69 | 70 | fmt.Println("写入多个文档 ", ret2.InsertedIDs) 71 | } 72 | ``` 73 | 74 | ### 查询 75 | ```golang 76 | func find() { 77 | var result Student 78 | err := collection.FindOne(nil, bson.D{{"name", "amy"}, {"age", 25}}).Decode(&result) 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | 83 | fmt.Printf("%+v\n", result) 84 | findOptions := options.Find() 85 | findOptions.SetLimit(3) 86 | 87 | var results []*Student 88 | 89 | cur, err := collection.Find(context.TODO(), bson.D{{}}, findOptions) 90 | if err != nil { 91 | log.Fatal(err) 92 | } 93 | 94 | for cur.Next(context.TODO()) { 95 | var elem Student 96 | err := cur.Decode(&elem) 97 | if err != nil { 98 | log.Fatal(err) 99 | } 100 | results = append(results, &elem) 101 | } 102 | 103 | if err := cur.Err(); err != nil { 104 | log.Fatal(err) 105 | } 106 | cur.Close(nil) 107 | 108 | for _,r := range results { 109 | fmt.Printf("%+v\n", r) 110 | } 111 | } 112 | ``` 113 | ### 更新 114 | ```golang 115 | filter := bson.D{{"no", "8001"}} 116 | update := bson.D{ 117 | {"$set", bson.D{ 118 | {"age", 33}, 119 | {"name", "wida2"}, 120 | }}, 121 | } 122 | ret, err := collection.UpdateOne(nil, filter, update) 123 | if err != nil { 124 | log.Fatal(err) 125 | } 126 | 127 | fmt.Printf("Matched %v documents and updated %v documents.\n", ret.MatchedCount, ret.ModifiedCount) 128 | } 129 | ``` 130 | 131 | ### 删除 132 | ```golang 133 | func del() { 134 | deleteResult, err := collection.DeleteMany(context.TODO(), bson.D{{}}) // 这边删除全部文档 135 | if err != nil { 136 | log.Fatal(err) 137 | } 138 | fmt.Printf("Deleted %v documents\n", deleteResult.DeletedCount) 139 | } 140 | ``` 141 | 142 | ## 参考文档 143 | - [MongoDB Go Driver](https://docs.mongodb.com/ecosystem/drivers/go/) 144 | - [MongoDB Go Driver Tutorial](https://www.mongodb.com/blog/post/mongodb-go-driver-tutorial) -------------------------------------------------------------------------------- /src/go和数据库/go使用mysql.md: -------------------------------------------------------------------------------- 1 | # go使用mysql 2 | 3 | go的mysql驱动相对比较统一,基本都用[go-sql-driver](github.com/go-sql-driver/mysql)。这个驱动也实现了数据库连接池。但是没有实现orm。 4 | 目前go的mysql orm实现则比较多 [gorm](https://github.com/jinzhu/gorm)、[xorm](https://github.com/go-xorm/xorm)、[beego-orm](https://github.com/astaxie/beego/tree/master/orm)。orm使用起来比较灵活,但是在大型项目中,由于orm屏蔽底层细节的原因通常不建议使用。本文主要介绍`go-sql-driver`不介绍rom。 5 | 6 | ## 创建一个测试table 7 | ```sql 8 | CREATE TABLE `tb_test` ( 9 | `id` bigint(11) NOT NULL AUTO_INCREMENT, 10 | `uid` int(11) NOT NULL, 11 | `img` varchar(255) NOT NULL, 12 | PRIMARY KEY (`id`), 13 | KEY `uid` (`uid`) 14 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 15 | ``` 16 | 17 | ## 连接mysql 18 | 导入 `github.com/go-sql-driver/mysql`包 19 | ```golang 20 | "database/sql" 21 | _ "github.com/go-sql-driver/mysql" 22 | ``` 23 | 注意这边使用 `_`用来执行这个包的`init()`函数,来注册mysql驱动,`sql.Register("mysql", &MySQLDriver{})` 24 | 25 | 26 | 连接mysql,注意连接池的主要三个配置`ConnMaxLifetime`,`MaxOpenConns`,`MaxIdleConns` 27 | ```golang 28 | var db *sql.DB 29 | func init() { 30 | var err error 31 | db, err = sql.Open("mysql", "user:password@tcp(localhost:3306)/test") 32 | if err != nil { 33 | panic(err.Error()) 34 | } 35 | db.SetConnMaxLifetime(10*time.Minute) 36 | db.SetMaxIdleConns(2) 37 | db.SetMaxOpenConns(10) 38 | } 39 | ``` 40 | 注意由于连接池的存在,一般只有确认不再使用msyql了才会调用`db.Close()`。 41 | 42 | 43 | ## curd操作 44 | ### 添加 45 | ```golang 46 | func add() { 47 | stmtIns, err := db.Prepare("INSERT INTO tb_test VALUES(?,?,?)") // ? = placeholder 48 | if err != nil { 49 | panic(err.Error()) 50 | } 51 | defer stmtIns.Close() 52 | for i := 0; i < 25; i++ { 53 | _, err = stmtIns.Exec(i+1, i * i,fmt.Sprintf("http://abc.com/%d.jpg",i)) 54 | if err != nil { 55 | panic(err.Error()) 56 | } 57 | } 58 | } 59 | ``` 60 | 61 | ### 获取 62 | ```golang 63 | func query() { 64 | //findone 65 | stmtOut, err := db.Prepare("SELECT uid FROM tb_test WHERE id = ?") 66 | if err != nil { 67 | panic(err.Error()) 68 | } 69 | defer stmtOut.Close() 70 | var squareNum int 71 | 72 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) //查询超时时间 73 | defer cancel() 74 | err = stmtOut.QueryRowContext(ctx,13).Scan(&squareNum) 75 | if err != nil { 76 | panic(err.Error()) 77 | } 78 | fmt.Printf("The square number of 13 is: %d \n", squareNum) 79 | err = stmtOut.QueryRow(5).Scan(&squareNum) //不带超时 80 | if err != nil { 81 | panic(err.Error()) 82 | } 83 | fmt.Printf("The square number of 5 is: %d \n", squareNum) 84 | 85 | //findmany 86 | stmtOut, err = db.Prepare("SELECT * FROM tb_test") 87 | if err != nil { 88 | panic(err.Error()) 89 | } 90 | defer stmtOut.Close() 91 | 92 | type Entry struct{ 93 | Id int32 94 | Uid int 95 | Img string} 96 | var entrys []Entry 97 | ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second) //查询超时时间,对耗时的查询使用超时处理对程序的健壮性有很大帮助 98 | defer cancel() 99 | rows ,err := stmtOut.QueryContext(ctx) 100 | if err!=nil { 101 | println(err) 102 | } 103 | defer rows.Close() 104 | for rows.Next() { 105 | entry := Entry{} 106 | rows.Scan(&entry.Id,&entry.Uid,&entry.Img) //这边需要和数据库的字段顺序保持一致,另外一种方法是select中指定字段,这边scan的顺序和指定的字段顺序一致 107 | entrys = append(entrys,entry) 108 | } 109 | fmt.Println(entrys) 110 | } 111 | 112 | ``` 113 | ### 更新 114 | ```golang 115 | func update() { 116 | stm,_ := db.Prepare("update tb_test set uid=? where id=? ") 117 | ret ,err := stm.Exec(999,1) 118 | if err !=nil { 119 | panic(err) 120 | } 121 | 122 | fmt.Println(ret.RowsAffected()) //影响条数 123 | stm.Close() 124 | } 125 | ``` 126 | ### 删除 127 | ```golang 128 | func delete() { 129 | stm,_ := db.Prepare("DELETE from tb_test where id=? ") 130 | ret ,err := stm.Exec(25) 131 | if err !=nil { 132 | panic(err) 133 | } 134 | fmt.Println(ret.RowsAffected()) //影响条数 135 | stm.Close() 136 | } 137 | ``` 138 | ### 事务 139 | ```golang 140 | func transaction() { 141 | tx ,_:=db.Begin() 142 | stmt,err := tx.Prepare("SELECT uid FROM tb_test WHERE id = ?") 143 | if err !=nil { 144 | tx.Rollback() 145 | return 146 | } 147 | var uid int32 148 | stmt.QueryRow(1).Scan(&uid) 149 | 150 | ret,err := tx.Exec("UPDATE tb_test set img='http://abc.com/3.jpg' where id=?",1) 151 | if err !=nil { 152 | tx.Rollback() 153 | return 154 | } 155 | fmt.Println(ret.RowsAffected()) 156 | 157 | tx.Commit() 158 | } 159 | ``` 160 | 161 | # 参考资料 162 | - [go-sql-driver](https://github.com/go-sql-driver/mysql) -------------------------------------------------------------------------------- /src/go实战/go_http2开发.md: -------------------------------------------------------------------------------- 1 | # go http2 开发 2 | 3 | ## 开发使用的https证书 4 | 5 | https证书对开发人员不是很友好,安装开发环境的证书都相对麻烦了点。这边介绍一个小工具[mkcert](https://github.com/FiloSottile/mkcert)。 6 | 7 | ### 安装 mkcert 8 | 本文以Ubuntu和Debian系统为例: 9 | 10 | - 安装依赖 `sudo apt install libnss3-tools` 11 | - 下载[mkcert](https://github.com/FiloSottile/mkcert/releases/download/v1.4.1/mkcert-v1.4.1-linux-amd64) 12 | - 生产证书`./mkcert -key-file key.pem -cert-file cert.pem example.com *.example.com localhost 127.0.0.1 ::1` 13 | - 添加本地信任 `mkcert -install` 14 | 15 | ## golang http2 16 | 17 | go 官方的http2包在[拓展网络库中](golang.org/x/net/http2),使用起来标准库中的http没啥区别。需要主要的是http2的包同时支持http2和http1当发现client端不支持http2的时候会使用http1。 18 | 19 | 我们新建一个项目http2test,同时把上面生成的`cert.pem`和`key.pem`拷贝到目录下面 20 | 21 | ``` 22 | $ tree 23 | . 24 | ├── cert.pem 25 | ├── go.mod 26 | ├── go.sum 27 | ├── key.pem 28 | └── main.go 29 | ``` 30 | 31 | ```go 32 | package main 33 | 34 | import ( 35 | "log" 36 | "net/http" 37 | "time" 38 | 39 | "golang.org/x/net/http2" 40 | ) 41 | 42 | const idleTimeout = 5 * time.Minute 43 | const activeTimeout = 10 * time.Minute 44 | 45 | func main() { 46 | var srv http.Server 47 | //http2.VerboseLogs = true 48 | srv.Addr = ":8972" 49 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 50 | w.Write([]byte("hello http2")) 51 | }) 52 | http2.ConfigureServer(&srv, &http2.Server{}) 53 | log.Fatal(srv.ListenAndServeTLS("cert.pem", "key.pem")) 54 | } 55 | ``` 56 | 57 | 运行程序 58 | 59 | ```bash 60 | $ go run main.go 61 | ``` 62 | 63 | 然后用chrome浏览器可以正常浏览 `https://localhost:8972/` 64 | 65 | 我们使用curl工具测试下http2和http1 client的情况: 66 | 67 | ```bash 68 | $ curl --http2 https://localhost:8972/ -v 69 | * Trying 127.0.0.1... 70 | * TCP_NODELAY set 71 | * Connected to localhost (127.0.0.1) port 8972 (#0) 72 | ... 73 | > GET / HTTP/2 74 | > Host: localhost:8972 75 | > User-Agent: curl/7.60.0 76 | > Accept: */* 77 | > 78 | * Connection state changed (MAX_CONCURRENT_STREAMS == 250)! 79 | < HTTP/2 200 80 | < content-type: text/plain; charset=utf-8 81 | < content-length: 11 82 | < date: Thu, 12 Dec 2019 11:13:26 GMT 83 | < 84 | * Connection #0 to host localhost left intact 85 | hello http2 86 | ``` 87 | 88 | 我们看到使用http2协议返回正常。 89 | 90 | 我们在看下使用http1的情况: 91 | 92 | ```bash 93 | $curl --http1.1 https://localhost:8972/ -v 94 | * Trying 127.0.0.1... 95 | ... 96 | * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 97 | * ALPN, server accepted to use http/1.1 98 | * Server certificate: 99 | * subject: O=mkcert development certificate; OU=wida@wida 100 | * start date: Jun 1 00:00:00 2019 GMT 101 | * expire date: Dec 12 10:47:26 2029 GMT 102 | * subjectAltName: host "localhost" matched cert's "localhost" 103 | * issuer: O=mkcert development CA; OU=wida@wida; CN=mkcert wida@wida 104 | * SSL certificate verify ok. 105 | > GET / HTTP/1.1 106 | > Host: localhost:8972 107 | > User-Agent: curl/7.60.0 108 | > Accept: */* 109 | > 110 | < HTTP/1.1 200 OK 111 | < Date: Thu, 12 Dec 2019 11:56:46 GMT 112 | < Content-Length: 11 113 | < Content-Type: text/plain; charset=utf-8 114 | < 115 | * Connection #0 to host localhost left intact 116 | hello http2 117 | ``` 118 | 119 | 正常返回内容`hello http2`但是我们看到的是走的http1.1的协议。 -------------------------------------------------------------------------------- /src/go实战/go程序物理内存占用高的问题.md: -------------------------------------------------------------------------------- 1 | # go程序物理内存占用高的问题 2 | 最近手头上的一个go项目,go版本是go1.13.1。项目上线后发现rss(Resident Set Size实际使用物理内存)很快就飚7g(服务器内存8g),而且很长时间内存都不下降。一开始还以为的内存泄露了。后来经过一番折腾才发现这个不是内存泄露。 3 | 4 | ## GODEBUG 5 | 先写一个模拟程序 6 | ```go 7 | package main 8 | import ( 9 | "runtime/debug" 10 | "time" 11 | ) 12 | func main() { 13 | num := 500000 14 | var bigmap = make(map[int]*[512]float32) 15 | for i := 0;i < num;i++ { 16 | bigmap[i] = &[512]float32{float32(i)} 17 | } 18 | 19 | println(len(bigmap)) 20 | time.Sleep(15e9) 21 | for i := 0;i < num;i++ { 22 | delete(bigmap,i) 23 | } 24 | 25 | debug.FreeOSMemory() 26 | time.Sleep(1000e9) 27 | } 28 | ``` 29 | 30 | ```bash 31 | $ go run main.go 32 | ``` 33 | 34 | 然后打开终端 35 | ```bash 36 | $ top -p pid 37 | ``` 38 | ![1](../img/1.png) 39 | 40 | 在系统负载比较低的时候,你会看到程序的Res 1G 左右,而且一直不变化。这个有点反直觉,我们向系统申请的50万个512维float32型的数组,后面实际上是已经删除了, 41 | 按理说golang的gc应该回收这个1g的内存,然后归还给系统才对,可是这样子的事情并没有发生。 42 | 到底发生了什么?我们去追踪下gc日志。 43 | ```bash 44 | $ GODEBUG=gctrace=1 go run main.go 45 | ... 46 | 500000 47 | gc 10 @16.335s 0%: 0.004+1.0+0.004 ms clock, 0.016+0/1.0/2.9+0.016 ms cpu, 1006->1006->0 MB, 1784 MB goal, 4 P (forced) 48 | scvg: 1 MB released 49 | scvg: inuse: 835, idle: 188, sys: 1023, released: 17, consumed: 1005 (MB) 50 | forced scvg: 1005 MB released 51 | forced scvg: inuse: 0, idle: 1023, sys: 1023, released: 1023, consumed: 0 (MB) 52 | ``` 53 | 我们可以在[go runtime](https://golang.org/pkg/runtime/) 看到gc日志的格式的相关信息 54 | 55 | ## gc日志 56 | 格式如下: 57 | ` gc # @#s #%: #+#+# ms clock, #+#/#/#+# ms cpu, #->#-># MB, # MB goal, # P` 58 | 59 | ``` 60 | gc 10 @16.335s 0%: 0.004+1.0+0.004 ms clock, 0.016+0/1.0/2.9+0.016 ms cpu, 1006->1006->0 MB, 1784 MB goal, 4 P (forced) 61 | ``` 62 | - gc 10: gc的流水号,从1开始自增 63 | - @16.335s: 从程序开始到当前打印是的耗时 64 | - 0%: 程序开始到当前CPU时间百分比 65 | - 0.004+1.0+0.004 ms clock: 0.004表示STW时间;1.0表示并发标记用的时间;0.004表示markTermination阶段的STW时间 66 | - 0.016+0/1.0/2.9+0.016 ms cpu: 0.016表示整个进程在mark阶段STW停顿时间;0/1.0/2.9有三块信息,0是mutator assists占用的时间,2.9是dedicated mark workers+fractional mark worker占用的时间,2.9+是idle mark workers占用的时间。0.016 ms表示整个进程在markTermination阶段STW停顿时间(0.050 * 8)。 67 | - 1006->1006->0 MB: GC开始、GC结束、存活的heap大小 68 | - 1784 MB goal:下一次触发GC的内存占用阈值 69 | - 4 P: 处理器数量 70 | - (forced): 可能没有,代表程序中runtime.GC() 被调用 71 | 72 | ## scvg日志: 73 | go语言把内存归还给操作系统的过程叫scavenging,scvg日志记录的就是这个过程的日志信息。 74 | scvg 每次会打印两条日志 75 | 格式: 76 | ``` 77 | scvg#: # MB released printed only if non-zero 78 | scvg#: inuse: # idle: # sys: # released: # consumed: # (MB) 79 | ``` 80 | ``` 81 | scvg: 1 MB released 82 | scvg: inuse: 835, idle: 188, sys: 1023, released: 17, consumed: 1005 (MB) 83 | ``` 84 | - 1 MB released: 返回给系统1m 内存 85 | - inuse: 正在使用的 86 | - idle :空闲的 87 | - sys : 系统映射内存 88 | - released:已经归还给系统的内存 89 | - consumed:已经向操作系统申请的内存 90 | 91 | 所以从上面的介绍来看,最后的归还日志 92 | ``` 93 | forced scvg: inuse: 0, idle: 1023, sys: 1023, released: 1023, consumed: 0 (MB) 94 | ``` 95 | 说明go语言已经正常把内存交给操作系统了了。可是RSS信息显示go仍然占用这这个内存。 96 | 这又是为什么? 97 | 在[go runtime](https://golang.org/pkg/runtime/)中我们找到一段如下的文字。 98 | ``` 99 | madvdontneed: setting madvdontneed=1 will use MADV_DONTNEED 100 | instead of MADV_FREE on Linux when returning memory to the 101 | kernel. This is less efficient, but causes RSS numbers to drop 102 | more quickly. 103 | ``` 104 | 翻译一下: 105 | 106 | ``` 107 | madvdontneed:如果设置GODEBUG=madvdontneed=1,golang归还内存给操作系统的方式将使用MADV_DONTNEED,而不是Linux上的MADV_FREE的方式。虽然MADV_DONTNEED效率较低,但会程序RSS下降得更快速。 108 | ``` 109 | 110 | 动手试一试 111 | ```bash 112 | $ GODEBUG=madvdontneed=1 go run main.go 113 | ``` 114 | ![2](../img/2.png) 115 | 这下RSS正常了。 116 | 117 | 118 | ## MADV_FREE和MADV_DONTNEED 119 | 120 | MADV 是linux kernel的内存回收的方式,有两种变体(MADV_FREE和MADV_DONTNEED,可以参考 121 | [MADV_FREE functionality](http://lkml.iu.edu/hypermail/linux/kernel/0704.3/3962.html)文档。 122 | 123 | ### 简单来说 124 | - MADV_FREE将内存页(page)延迟回收。当内核内存紧张时,这些内存页将会被优先回收,如果应用程序在页回收后又再次访问,内核将会返回一个新的并设置为0的页。而如果内核内存充裕时,标识为MADV_FREE的页会仍然存在,后续的访问会清掉延迟释放的标志位并正常读取原来的数据。 125 | - MADV_DONTNEED告诉内核这段内存今后"很可能"用不到了,其映射的物理内存尽管拿去好了,因此物理内存可以回收以做它用,但虚拟空间还留着,下次访问时会产生缺页中断,这个缺页中断会触发重新申请物理内存的操作。 126 | 127 | go1.12之后采用了MADV_FREE的方式,这样子个go程序和内核间的内存交互会更加高效,但是带来的一个副作用就是rss下降的比较慢(系统资源充裕时,甚至你感觉不到他是在下降)。在关注 `top` 信息的时候,别只关注RES和%MEM,还要关注下buff/cache。在系统资源紧张的时候MADV_FREE方式标记的内存也会很快回收,并不需要太担心。另外使用`GODEBUG=madvdontneed=1`会强制使用原先MADV_DONTNEED的方式。 128 | 129 | 130 | 131 | ## 补充 132 | - golang的内存申请方式会比较激进,像map扩容的时候一次申请的比实际需要的多得多。然后在delete map的时候内存不会释放。 133 | - 如果在程序中需要反复申请对象,然后销毁的话,应该使用`sync.pool`来重复使用申请过的内存,这样子可以让程序申请的系统内存相对平稳。 134 | 135 | # 参考资料 136 | - [GODEBUG之gctrace解析](http://cbsheng.github.io/posts/godebug%E4%B9%8Bgctrace%E8%A7%A3%E6%9E%90/) 137 | - [go runtime](https://golang.org/pkg/runtime/) 138 | - [Go内存泄漏?不是那么简单!](https://colobu.com/2019/08/28/go-memory-leak-i-dont-think-so/) 139 | - [MADV_FREE functionality](http://lkml.iu.edu/hypermail/linux/kernel/0704.3/3962.html) -------------------------------------------------------------------------------- /src/go实战/go语言结构体优雅初始化.md: -------------------------------------------------------------------------------- 1 | # go 语言结构图优雅初始化的一种方式 2 | 该方式存于go-micro,go-grpc中 3 | 4 | ```go 5 | package main 6 | import "fmt" 7 | type Config struct { 8 | A string 9 | B int 10 | } 11 | 12 | type option func(c *Config) 13 | 14 | func NewConig(o ...option) *Config{ 15 | c := Config{ 16 | A:"wida", 17 | B:3, 18 | } 19 | for _,op:= range o { 20 | op(&c) 21 | } 22 | return &c 23 | } 24 | 25 | func writeA(a string) option{ 26 | return func(c *Config) { 27 | c.A=a 28 | } 29 | } 30 | 31 | func writeB(b int) option{ 32 | return func(c *Config) { 33 | c.B = b 34 | } 35 | } 36 | 37 | func main() { 38 | a := writeA("amy") 39 | b := writeB(6) 40 | c := NewConig(a,b) 41 | fmt.Println(c) 42 | } 43 | ``` 44 | 45 | 运行: 46 | ```bash 47 | # go run main.go 48 | &{amy 6} 49 | ``` -------------------------------------------------------------------------------- /src/go并发编程/errgroup.md: -------------------------------------------------------------------------------- 1 | # errgroup 2 | 3 | ## 什么是errrgroup 4 | 5 | 在开发并发程序时,错误的收集和传播往往比较繁琐,有时候当一个错误发声时,我们需要停止所有相关任务,有时候却不是。`sync.ErrGroup`刚好可以解决我们上述的痛点,它提供错误传播,以及利用`context`的方式来决定是否要停止相关任务。 6 | 7 | `errrgroup.Group`结构体 8 | ```go 9 | type Group struct { 10 | cancel func() 11 | wg sync.WaitGroup 12 | errOnce sync.Once 13 | err error 14 | } 15 | `` 16 | 17 | 三个对外api 18 | 19 | ```go 20 | unc WithContext(ctx context.Context) (*Group, context.Context) 21 | func (g *Group) Go(f func() error) 22 | func (g *Group) Wait() error 23 | ``` 24 | 25 | ## 使用errrgroup 26 | 27 | ### 只返回错误 28 | 29 | ```go 30 | package main 31 | 32 | import ( 33 | "fmt" 34 | "golang.org/x/sync/errgroup" 35 | "net/http" 36 | ) 37 | 38 | func main() { 39 | var g errgroup.Group 40 | var urls = []string{ 41 | "http://www.golang.org/", 42 | "http://www.111111111111111111111111.com/", //这个地址不存在 43 | "http://www.google.com/", 44 | "http://www.somestupidname.com/", 45 | } 46 | for _, url := range urls { 47 | url := url 48 | g.Go(func() error { 49 | resp, err := http.Get(url) 50 | if err == nil { 51 | resp.Body.Close() 52 | } 53 | return err 54 | }) 55 | } 56 | if err := g.Wait(); err == nil { 57 | fmt.Println("Successfully fetched all URLs.") 58 | }else { 59 | fmt.Println(err) 60 | } 61 | } 62 | ``` 63 | 64 | ```bash 65 | $ go run main.go 66 | www.111111111111111111111111.com:80: unknown error host unreachable 67 | ``` 68 | 69 | ### 使用 errgroup.WithContext 70 | 71 | ```go 72 | package main 73 | 74 | import ( 75 | "context" 76 | "fmt" 77 | "golang.org/x/sync/errgroup" 78 | "net/http" 79 | "time" 80 | ) 81 | 82 | func main() { 83 | ctx ,_:=context.WithTimeout(context.Background(),3*time.Second) 84 | var g ,_= errgroup.WithContext(ctx) 85 | var urls = []string{ 86 | "http://www.golang.org/", 87 | "http://www.111111111111111111111111.com/", 88 | "http://www.google.com/", 89 | "http://www.somestupidname.com/", 90 | } 91 | for _, url := range urls { 92 | url := url 93 | g.Go(func() error { 94 | ch := make(chan error) 95 | go func() { 96 | time.Sleep(4e9) 97 | resp, err := http.Get(url) 98 | if err == nil { 99 | resp.Body.Close() 100 | } 101 | ch <- err 102 | }() 103 | select { 104 | case err:= <-ch : 105 | return err 106 | case <-ctx.Done(): 107 | return ctx.Err() 108 | } 109 | }) 110 | } 111 | if err := g.Wait(); err == nil { 112 | fmt.Println("Successfully fetched all URLs.") 113 | }else { 114 | fmt.Println(err) 115 | } 116 | } 117 | ``` 118 | 119 | ```bash 120 | $ go run main.go 121 | context deadline exceeded 122 | ``` -------------------------------------------------------------------------------- /src/go并发编程/goroutine同步.md: -------------------------------------------------------------------------------- 1 | # goroutine同步 2 | 3 | ## 互斥锁(sync.Mutex)和读写锁(sync.RWMutex) 4 | 5 | 类似其他语言,golang也提供了互斥锁和读写锁的的同步原语。 6 | 7 | 我们先看下go中互斥锁(sync.Mutex)的使用。 8 | 9 | ```golang 10 | package main 11 | import ( 12 | "fmt" 13 | "sync" 14 | "time" 15 | ) 16 | func main() { 17 | a := 0 18 | for i:=0;i< 100;i++ { 19 | go func() { 20 | a += 1 21 | }() 22 | } 23 | time.Sleep(1e9) 24 | fmt.Println(a) 25 | a = 0 26 | mutex := sync.Mutex{} 27 | for i:=0;i< 100;i++ { 28 | go func() { 29 | mutex.Lock() 30 | defer mutex.Unlock() 31 | a ++ 32 | }() 33 | } 34 | time.Sleep(1e9) 35 | fmt.Println(a) 36 | } 37 | ``` 38 | 39 | ```bash 40 | $ go run main.go 41 | 85 42 | 100 43 | ``` 44 | 45 | 我们在看下读写锁(sync.RWMutex)。 46 | 47 | 读写锁:读的时候不会阻塞读,会阻塞写;写的时候会阻塞写和读,我们可以用这个特性实现线程安全的map。 48 | 49 | ```golang 50 | import "sync" 51 | type safeMap struct { 52 | rwmut sync.RWMutex 53 | Map map[string]int 54 | } 55 | 56 | func (sm *safeMap)Read(key string)(int,bool){ 57 | sm.rwmut.RLock() 58 | defer sm.rwmut.RUnlock() 59 | if v,found := sm.Map[key];!found { 60 | return 0,false 61 | }else { 62 | return v,true 63 | } 64 | } 65 | 66 | func (sm *safeMap)Set(key string,val int) { 67 | sm.rwmut.Lock() 68 | defer sm.rwmut.Unlock() 69 | sm.Map[key] = val 70 | } 71 | ``` 72 | 73 | ## WaitGroup 74 | 75 | WaitGroup 常用在等goroutine结束。 76 | 77 | ```golang 78 | package main 79 | import ( 80 | "fmt" 81 | "sync" 82 | "time" 83 | ) 84 | func main() { 85 | wg :=sync.WaitGroup{} 86 | wg.Add(2) //这边说明有2个任务 87 | go func() { 88 | defer wg.Done() //代表任务1结束 89 | time.Sleep(1e9) 90 | fmt.Println("after 1 second") 91 | }() 92 | 93 | go func() { 94 | defer wg.Done()//代表任务2结束 95 | time.Sleep(2e9) 96 | fmt.Println("after 2 second") 97 | }() 98 | wg.Wait() //等待所有任务结束 99 | fmt.Println("the end") 100 | } 101 | ``` 102 | 103 | ```bash 104 | after 1 second 105 | after 2 second 106 | the end 107 | ``` 108 | 109 | 110 | ## 总结 111 | 112 | 本小节列举了goroutine使用传统的`Mutex`和`WaitGroup`做"线程"同步的例子,在go语言中,官方注重推荐使用`channel`做“线程”同步,下个小节我们着重介绍`channel`的使用。 -------------------------------------------------------------------------------- /src/go并发编程/semaphore.md: -------------------------------------------------------------------------------- 1 | # Semaphore(信号量) 2 | 3 | ## 什么是Semaphore(信号量,信号标) 4 | 5 | Semaphore是一种同步对象,用来存0到指定值的计数值。信号量如果计数值可以是任意整数则称为计数信号量(一般信号量),如果值只能是0和1则叫二进制信号量,也就是我们常见的互斥锁(Mutex)。 6 | 7 | 计数信号量具备两种操作动作,称为V与P。V操作会增加信号量计数器的数值,P操作会减少它。go标准库没有实现信号量,但是拓展同步库里头实现了(`golang.org/x/sync/semaphore`)计数信号量叫做 `Weighted`——加权信号量。 8 | 9 | 我们先看下`Weighted`结构体 10 | ```go 11 | type Weighted struct { 12 | size int64 13 | cur int64 14 | mu sync.Mutex 15 | waiters list.List 16 | } 17 | ``` 18 | 19 | `Weighted`有四个对外api 20 | ```go 21 | func NewWeighted(n int64) *Weighted 22 | func (s *Weighted) Acquire(ctx context.Context, n int64) error 23 | func (s *Weighted) Release(n int64) 24 | func (s *Weighted) TryAcquire(n int64) bool 25 | ``` 26 | 27 | 运作方式: 28 | 29 | 初始化,给与它一个整数值。 30 | 运行P(Acquire),信号标S(size)的值将被减少。企图进入临界区段的goroutine,需要先运行P(Acquire)。当信号标S(size)减为负值时,goroutine会被阻塞,不能继续;当信号标S(size)不为负值时,goroutine可以获准进入临界区段。 31 | 运行V(Release),信号标S(size)的值会被增加。结束离开临界区段的goroutine,将会运行V(Release)。当信号标S(size)不为负值时,先前被挡住的其他goroutine,将可获准进入临界区段。 32 | 33 | TryAcquire的作用和Acquire类似,但是TryAcquire不会阻塞,发现进不去临界区段就会返回false。 34 | 35 | ## 使用Semaphore 36 | 37 | 我们在`channel`做goroutine同步的时候介绍了用`channel`控制goroutine数量并发数量的例子。信号量非常适合做类似的事情。 38 | 我们写一个控制goroutine并发数为机器cpu核数的程序程序。 39 | 40 | ```go 41 | package main 42 | 43 | import ( 44 | "context" 45 | "fmt" 46 | "log" 47 | "runtime" 48 | "time" 49 | "golang.org/x/sync/semaphore" 50 | ) 51 | 52 | func main() { 53 | ctx := context.TODO() 54 | var ( 55 | maxWorkers = runtime.GOMAXPROCS(0) 56 | sem = semaphore.NewWeighted(int64(maxWorkers)) 57 | out = make([]int, 8) 58 | ) 59 | fmt.Println(maxWorkers) 60 | for i := range out { 61 | //fmt.Println(sem.TryAcquire(1)) 62 | if err := sem.Acquire(ctx, 1); err != nil { 63 | log.Printf("Failed to acquire semaphore: %v", err) 64 | break 65 | } 66 | go func(i int) { 67 | fmt.Printf("goroutine %d \n",i) 68 | defer sem.Release(1) 69 | time.Sleep(1e9) 70 | }(i) 71 | } 72 | 73 | if err := sem.Acquire(ctx, int64(maxWorkers)); err != nil { //这边会等到size为初始值再返回,这总方式可以实现类似`sycn.WaitGroup`的功能 74 | log.Printf("Failed to acquire semaphore: %v", err) 75 | } 76 | } 77 | ``` 78 | 79 | ```bash 80 | $ go run main.go 81 | 4 82 | goroutine 3 83 | goroutine 0 84 | goroutine 1 85 | goroutine 2 86 | goroutine 4 #时间间隔1s后打印 87 | goroutine 5 88 | goroutine 6 89 | goroutine 7 90 | ``` 91 | 92 | `Weighted`的`Release`方法会按照FIFO(队列)的顺序唤醒阻塞goroutine。在实际开发中控制并最大并发数的同时还需要防止超长时间的goroutine,所以`sem.Acquire`带了`context`参数。 93 | 94 | ## 总结 95 | 96 | 本小节介绍了信号量的运行方式,以及介绍了go semaphore(golang.org/x/sync/semaphore)的使用。 -------------------------------------------------------------------------------- /src/go并发编程/singleflight.md: -------------------------------------------------------------------------------- 1 | # singleflight 2 | 3 | ## 什么是singleflight 4 | 5 | `singleflight`是go拓展同步库中实现的一种针对重复的函数调用抑制机制,也就是说一组相同的操作(函数),只有一个函数能被执行,执行后的结果返回给其他同组的函数。`singleflight`应该算是一种并发模型,非常适合当redis某个key缓存失效时,这个时候一堆请求去数据库来取数据然后更新redis缓存,如果我们使用`singlefilght`并发模型的话,那就是redis key失效的时候,一堆去数据库的请求只有一个能成功,它将更新该redis key的value值,同时把value值给其他相同请求。 6 | 7 | `singlefilght`中的`Group`结构体 8 | 9 | ```go 10 | type Group struct { 11 | mu sync.Mutex // 互斥锁保护 m 12 | m map[string]*call // 存形同key的 函数 13 | } 14 | ``` 15 | 16 | `singleflight`有三个对外api 17 | 18 | ```go 19 | func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) 20 | func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result 21 | func (g *Group) Forget(key string) 22 | ``` 23 | 24 | `Group.Do`和`Group.DoChan`功能类似,`Do`是同步返回,`DoChan`返回一个chan实现异步返回。 25 | `Group.Forget`方法可以通知 `singleflight` 在map删除某个key,接下来对该key的调用就会直接执行方法而不是等待前面的函数返回。 26 | 27 | ## 使用singleflight 28 | 29 | ```go 30 | package main 31 | 32 | import ( 33 | "fmt" 34 | "github.com/go-redis/redis" 35 | "golang.org/x/sync/singleflight" 36 | "sync" 37 | "sync/atomic" 38 | ) 39 | 40 | func main() { 41 | client := redis.NewClient(&redis.Options{ 42 | Addr: "127.0.0.1:6379"}) 43 | 44 | var g singleflight.Group 45 | var callTimes int32 =0 46 | fakeGetData := func() int32 {//模拟去数据库取数据,记录被调用次数 47 | callTimes = atomic.AddInt32(&callTimes,1) 48 | return callTimes 49 | } 50 | 51 | wg := sync.WaitGroup{} 52 | for i:=0;i<10;i++ { //模拟10并发 53 | wg.Add(1) 54 | go func() { 55 | defer wg.Done() 56 | ret,err,_:= g.Do("wida", func() (i interface{}, e error) { 57 | num := fakeGetData() 58 | client.Set("wida",num,0) 59 | return num,nil 60 | }) 61 | fmt.Println(ret,err) 62 | }() 63 | } 64 | wg.Wait() 65 | fmt.Printf("callTimes %d \n",callTimes) 66 | ret,_:=client.Get("wida").Int() 67 | fmt.Printf("redis value %d \n",ret) 68 | } 69 | ``` 70 | 71 | 运行结果 72 | 73 | ```bash 74 | $ go run main.go 75 | 1 76 | 1 77 | 1 78 | 1 79 | 1 80 | 1 81 | 1 82 | 1 83 | 1 84 | 1 85 | callTimes 1 86 | redis value 1 87 | ``` 88 | 89 | 我们看到10个并发请求,`fakeGetData`只被调用了一次,reids的值也被设置为1,10个请求拿到了相同的结果。 90 | 91 | 如果要使用`DoChan`的方式只需要稍微修改下 92 | ```go 93 | go func() { 94 | defer wg.Done() 95 | retChan:= g.DoChan("wida", func() (i interface{}, e error) { 96 | num := fakeGetData() 97 | client.Set("wida",num,0) 98 | return num,nil 99 | }) 100 | ret := <- retChan 101 | fmt.Println(ret) 102 | }() 103 | ``` 104 | 105 | 106 | ## 总结 107 | 108 | 本小节介绍了go拓展同步库中`singleflight`(golang.org/x/sync/singleflight),以及介绍了`singleflight`使用方式和适合的场景。 -------------------------------------------------------------------------------- /src/go并发编程/sync_map.md: -------------------------------------------------------------------------------- 1 | # 并发安全map —— sync.Map 2 | 3 | go原生的map不是线程安全的,在并发读写的时候会触发`concurrent map read and map write`的panic。 4 | `map`应该是属于go语言非常高频使用的数据结构。早期go标准库并没有提供线程安全的`map`,开发者只能自己实现,后面go吸取社区需求提供了线程安全的map——`sync.Map`。 5 | 6 | `sync.Map` 提供5个如下api: 7 | ```go 8 | func (m *Map) Delete(key interface{}) //删除这个key的value 9 | func (m *Map) Load(key interface{}) (value interface{}, ok bool) //加载这个key的value 10 | func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) //原子操作加载,如果没有则存储 11 | func (m *Map) Range(f func(key, value interface{}) bool) //遍历kv 12 | func (m *Map) Store(key, value interface{}) //存储 13 | ``` 14 | 15 | ## 使用sync.Map 16 | 17 | ```go 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | "sync" 23 | ) 24 | 25 | func main() { 26 | sMap := sync.Map{} 27 | sMap.Store("a","b") 28 | ret,_:= sMap.Load("a") 29 | fmt.Printf("ret1 %t \n",ret.(string) == "b" ) 30 | ret,loaded :=sMap.LoadOrStore("a","c") 31 | fmt.Printf("ret2 %t loaded:%t \n",ret.(string) == "b",loaded ) 32 | ret,loaded =sMap.LoadOrStore("d","c") 33 | fmt.Printf("loaded %t \n",loaded) 34 | sMap.Store("e","f") 35 | sMap.Delete("e") 36 | sMap.Range(func(key, value interface{}) bool { 37 | fmt.Printf("k:%s v:%s \n", key.(string),value.(string)) 38 | return true 39 | }) 40 | } 41 | ``` 42 | 43 | ```bash 44 | $ go run main.go 45 | ret1 true 46 | ret2 true loaded:true 47 | loaded false 48 | k:a v:b 49 | k:d v:c 50 | ``` 51 | 52 | ## sync.Map底层实现 53 | 54 | ### `sync.Map`的结构体 55 | 56 | ```go 57 | type Map struct { 58 | mu Mutex //互斥锁保护dirty 59 | read atomic.Value //存读的数据,只读并发安全,存储的数据类型为readOnly 60 | dirty map[interface{}]*entry //包含最新写入的数据,等misses到阈值会上升为read 61 | misses int //计数器,当读read的时候miss了就加+ 62 | } 63 | ``` 64 | 65 | ```go 66 | type readOnly struct { 67 | m map[interface{}]*entry 68 | amended bool //dirty的数据和这里的m中的数据有差异的时候true 69 | } 70 | ``` 71 | 72 | 从结构体上看Map有一定指针数据冗余,但是因为是指针数据,所以冗余的数据量不大。 73 | 74 | ### `Load`的源码: 75 | 76 | ```go 77 | func (m *Map) Load(key interface{}) (value interface{}, ok bool) { 78 | read, _ := m.read.Load().(readOnly) //读只读的数据 79 | e, ok := read.m[key] 80 | if !ok && read.amended { //如果没有读到且read的数据和drity数据不一致的时候 81 | m.mu.Lock() 82 | read, _ = m.read.Load().(readOnly) //加锁后二次确认 83 | e, ok = read.m[key] 84 | if !ok && read.amended { //如果没有读到且 read的数据和drity数据不一致的时候 85 | e, ok = m.dirty[key] 86 | m.missLocked() //misses +1,如果 misses 大于等于 m.dirty 则发送 read的值指向ditry 87 | } 88 | m.mu.Unlock() 89 | } 90 | if !ok { 91 | return nil, false 92 | } 93 | return e.load() 94 | } 95 | ``` 96 | 97 | ### `Store`的源码: 98 | 99 | ```go 100 | func (m *Map) Store(key, value interface{}) { 101 | read, _ := m.read.Load().(readOnly) 102 | if e, ok := read.m[key]; ok && e.tryStore(&value) { //如果在read中找到则尝试更新,tryStore中判断key是否已经被标识删除,如果已经被上传则更新不成功 103 | return 104 | } 105 | 106 | m.mu.Lock() 107 | read, _ = m.read.Load().(readOnly) //同上二次确认 108 | if e, ok := read.m[key]; ok { 109 | if e.unexpungeLocked() {// 如果entry被标记expunge,则表明dirty没有key,可添加入dirty,并更新entry。 110 | m.dirty[key] = e 111 | } 112 | e.storeLocked(&value) 113 | } else if e, ok := m.dirty[key]; ok { //如果dirty存在该key 114 | e.storeLocked(&value) 115 | } else { //key不存在 116 | if !read.amended { //read 和 dirty 一致 117 | // 将read中未删除的数据加入到dirty中 118 | m.dirtyLocked() 119 | // amended标记为read与dirty不一致,因为即将加入新数据。 120 | m.read.Store(readOnly{m: read.m, amended: true}) 121 | } 122 | m.dirty[key] = newEntry(value) 123 | } 124 | m.mu.Unlock() 125 | } 126 | ``` 127 | 128 | ### `Delete`的源码: 129 | 130 | ```go 131 | // Delete deletes the value for a key. 132 | func (m *Map) Delete(key interface{}) { 133 | read, _ := m.read.Load().(readOnly) 134 | e, ok := read.m[key] 135 | if !ok && read.amended { //在read中没有找到且 read和dirty不一致 136 | m.mu.Lock() 137 | read, _ = m.read.Load().(readOnly) //加锁二次确认 138 | e, ok = read.m[key] 139 | if !ok && read.amended { 140 | delete(m.dirty, key) //从dirty中删除 141 | } 142 | m.mu.Unlock() 143 | } 144 | if ok { //如果key在read中存在 145 | e.delete() //将指针置为nil,标记删除 146 | } 147 | } 148 | ``` 149 | ## 优缺点 150 | 151 | 优点: 通过read和dirty冗余的方式实现读写分离,减少锁频率来提高性能。 152 | 缺点:大量写的时候会导致read读不到数据而进一步加锁读取dirty,同时多次miss的情况下dirty也会频繁升级为read影响性能。 153 | 因此`sync.Map`的使用场景应该是读多,写少。 154 | 155 | ## 总结 156 | 157 | 本小节介绍了`sync.Map`的使用,通过源码的方式了解`sync.Map`的底层实现,同时介绍了它的优缺点,以及使用场景。 -------------------------------------------------------------------------------- /src/go并发编程/sync_once.md: -------------------------------------------------------------------------------- 1 | # sync.Once 2 | 3 | sync.Once提供一种机制来保证某个函数只执行一次,常常用于初始化对象。 4 | 只有一个api: 5 | ```go 6 | func (o *Once) Do(f func()) 7 | ``` 8 | 9 | 我们来写个demo 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | "sync" 17 | "time" 18 | ) 19 | 20 | func main() { 21 | once := sync.Once{} 22 | a :=1 23 | i:=0 24 | for i <10{ 25 | go func() { 26 | once.Do(func() { 27 | a++ 28 | }) 29 | }() 30 | i++ 31 | } 32 | time.Sleep(1e9) 33 | fmt.Println(a) 34 | } 35 | ``` 36 | 37 | ```bash 38 | $ go run main.go 39 | 2 40 | ``` 41 | 上面的例子中,Once.Do 被执行了十次,他包裹的函数却只被执行了一次。 42 | 43 | 我们来看下`sync.Once`的源码: 44 | 45 | ```go 46 | type Once struct { 47 | done uint32 //状态值 48 | m Mutex //互斥锁 49 | } 50 | 51 | func (o *Once) Do(f func()) { 52 | if atomic.LoadUint32(&o.done) == 0 { //等于0的时候代表没被执行过 53 | o.doSlow(f) 54 | } 55 | } 56 | 57 | func (o *Once) doSlow(f func()) { 58 | o.m.Lock() //确保只有一个goroutine能到锁 59 | defer o.m.Unlock() 60 | if o.done == 0 { 61 | defer atomic.StoreUint32(&o.done, 1) //原子操作改变状态 62 | f() 63 | } 64 | } 65 | ``` 66 | 67 | 从源码上看,他是利用`sync.Mutex`和原子操作来实现的。 68 | -------------------------------------------------------------------------------- /src/go并发编程/sync_pool.md: -------------------------------------------------------------------------------- 1 | # sync.Pool(临时对象池) 2 | 3 | ## 什么是sync.Pool 4 | 5 | golang是带GC(垃圾回收)的语言,如果高频率的生成对象,然后有废弃这样子会给gc带来很大的负担,而且在go的内存申请上也会出现比较大的抖动。那么有什么办法减少gc负担,重用这些对象,然后又能让go的内存平缓些呢?答案是使用`sync.Pool`。 6 | 7 | `sync.Pool`是golang用来存储临时对象的,这些对象通常是高频率生成销毁(这边还需要注意下,我们所说的对象是堆内存上的,而不是在栈内存上)。 8 | 9 | ## 使用sync.Pool 10 | 11 | sync.Pool 有两个对外API 12 | 13 | ``` 14 | func (p *Pool) Get() interface{} 15 | func (p *Pool) Put(x interface{}) 16 | ``` 17 | 另外`sync.Pool`对象初始化的时候需要指定属性`New`是一个 `func() interface{}`函数类型,用来在没有可复用对象时重新生成对象。 18 | 19 | 其实`sync.Pool`的使用非常频繁,不管事go标准库还是第三方库都非常多的使用。 20 | 在标准库`fmt`就使用到`sync.Pool`,我们追踪下`fmt.Printf`的源码: 21 | 22 | ```go 23 | func Printf(format string, a ...interface{}) (n int, err error) { 24 | return Fprintf(os.Stdout, format, a...) 25 | } 26 | 27 | func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { 28 | p := newPrinter() 29 | p.doPrintf(format, a) 30 | n, err = w.Write(p.buf) 31 | p.free()zongj 32 | return 33 | } 34 | 35 | var ppFree = sync.Pool{ 36 | New: func() interface{} { return new(pp) }, //指定生成对象函数 37 | } 38 | 39 | func newPrinter() *pp { 40 | p := ppFree.Get().(*pp) //从pool中获取可复用对象,如果没有对象池会重新生成一个,注意这边拿到对象后会reset对象 41 | p.panicking = false 42 | p.erroring = false 43 | p.wrapErrs = false 44 | p.fmt.init(&p.buf) 45 | return p 46 | } 47 | 48 | func (p *pp) free() { 49 | if cap(p.buf) > 64<<10 { 50 | return 51 | } 52 | p.buf = p.buf[:0] 53 | p.arg = nil 54 | p.value = reflect.Value{} 55 | p.wrappedErr = nil 56 | ppFree.Put(p) //用完后重新放到pool中 57 | } 58 | ``` 59 | 60 | 从上面的案例中大概可以看出`sync.Pool`是如何使用的。接下来我们写一个demo程序,看下另外一个`sync.Pool`的高频使用场景 61 | 62 | 63 | ```go 64 | package main 65 | 66 | import ( 67 | "io" 68 | "log" 69 | "net" 70 | "sync" 71 | ) 72 | 73 | func main() { 74 | bufpool := sync.Pool{} 75 | bufpool.New = func() interface{} { 76 | return make([]byte, 32768) 77 | } 78 | Pipe := func(c1, c2 io.ReadWriteCloser) { 79 | b := bufpool.Get().([]byte) 80 | b2 := bufpool.Get().([]byte) 81 | defer func() { 82 | bufpool.Put(b) 83 | bufpool.Put(b2) 84 | c1.Close() 85 | c2.Close() 86 | }() 87 | 88 | go io.CopyBuffer(c1, c2, b) 89 | io.CopyBuffer(c2, c1, b2) 90 | } 91 | l,err := net.Listen("tcp",":9999") 92 | if err !=nil { 93 | log.Fatal(err) 94 | } 95 | for { 96 | conn,err := l.Accept() 97 | if err !=nil { 98 | log.Fatal(err) 99 | } 100 | client ,err:= net.Dial("tcp","127.0.0.1:80") 101 | if err !=nil { 102 | log.Fatal(err) 103 | } 104 | go Pipe(conn,client) 105 | } 106 | } 107 | ``` 108 | 这是个的代理程序,任何连到本机9999端口的tcp链接都会转发到本地的80端口。我们使用`io.CopyBuffer`实现数据双工互相拷贝。 109 | `io.CopyBuffer`会频繁使用到缓存`[]byte`对象,我们用`sync.Pool`重复使用`[]byte`. 110 | 111 | 运行一下程序 112 | ```bash 113 | $ go run main.go 114 | $ curl http://localhost:9999 115 | ``` 116 | 117 | ## 总结 118 | 119 | 本小节介绍了`sync.Pool`的使用方式。`sync.Pool`能减轻go GC的负担,同时减少内存的分配,是保障go程序内存分配平缓的重要手段。 -------------------------------------------------------------------------------- /src/go并发编程/并发和并行.md: -------------------------------------------------------------------------------- 1 | # 并发和并行 2 | 3 | ## 并发不是并行 4 | 5 | golang被成为原生支持并发的语言,那么什么是并发? 6 | go语言之父Rob Pike就专门回答过这个问题,并且和做了一个 [并发不是并行的演讲](https://talks.golang.org/2012/waza.slide)。 7 | 8 | 并发(Concurrency)将相互独立的执行过程综合到一起的编程技术。 9 | 10 | 并行(Parallelism)同时执行(通常是相关的)计算任务的编程技术。 11 | 12 | ![](../img/concurreat.jpg) 13 | 14 | 上图是Erlang 之父 Joe Armstrong来解释并发和并行。并发是两队伍交替使用咖啡机,并行是两个队伍两个咖啡机。 15 | 16 | 17 | 并发是指同时处理很多事情,主要关注的是流程合理优化组合,这一点很像我们小时候学的《统筹时间》关于如何优化任务顺序然后更有效率。 18 | 19 | 而并行是指同时能完成很多事情。 20 | 21 | 两者不同,但相关。 22 | 23 | 一个注重点是组合,一个注重点是执行。 24 | 25 | 并发提供了一种方式让我们能够设计一种方案将问题并行的解决。 26 | 27 | ## CSP并发模型 28 | 29 | 传统的并发模型(例如Java,C ++和Python程序时通常使用)要求程序员使用共享内存在线程之间进行通信。通常共享数据结构用锁保护,线程将争夺这些锁以访问数据。 30 | golang虽然也支持这种并发模型,但是go更鼓励使用CSP(Communicating Sequential Processe,CSP)并发模型。CSP描述两个独立的并发实体(goroutine)通过共享的通讯 channel(管道)进行通信的并发模型。CSP中channel是第一类对象,它不关注发送消息的实体,而关注与发送消息时使用的channel。 31 | 32 | “Don’t communicate by sharing memory, share memory by communicating”——不要通过共享内存来通信,而应该通过通信来共享内存。 33 | 34 | 35 | # 参考资料 36 | 37 | - [Concurrency is not Parallelism](https://talks.golang.org/2012/waza.slide) -------------------------------------------------------------------------------- /src/go并发编程/条件变量.md: -------------------------------------------------------------------------------- 1 | # 条件变量-sync.Cond 2 | 3 | 4 | ## 什么是条件变量 5 | 6 | 与sync.Mutex不同,sync.Cond的作用是在对应的共享数据的状态发生变化时,通知一个或者所以因为共享数据而被阻塞的goroutine。sync.Cond总是与sync.Mutex(或者sync.RWMutex)组合使用。sync.Mutex为共享数据的访问提供互斥锁支持,而sync.Cond可以在共享数据的状态的变化向相关goroutine发出通知。 7 | 8 | ## sync.Cond api 9 | 10 | sync.Cond总共有3个方法,一个`NewCond`创建函数 11 | ``` 12 | func NewCond(l Locker) *Cond # 创建NewCond 参数需要是一个`Locker` 一般使用 `sync.Mutex`或者`sync.RWMutex` 13 | func (c *Cond) Wait() #阻塞当前goroutine,通过`Signal`或者`Broadcast`唤醒 14 | func (c *Cond) Signal() #唤醒一个被阻塞的goroutine,如果没有的话会忽略。 15 | func (c *Cond) Broadcast() #唤醒一个所有阻塞的goroutine。 16 | ``` 17 | 18 | 我们从`sync.Cond`提过的api可以看到,它有两种模式,单播和广播。 19 | 20 | ## 单播 21 | 22 | ```go 23 | package main 24 | 25 | import ( 26 | "fmt" 27 | "sync" 28 | "time" 29 | ) 30 | 31 | func main() { 32 | cond := sync.NewCond(new(sync.Mutex)) 33 | condition := 0 34 | // Consumer 35 | go func() { 36 | for { 37 | cond.L.Lock() 38 | for condition == 0 { 39 | cond.Wait() 40 | } 41 | condition-- 42 | fmt.Printf("Consumer: %d\n", condition) 43 | cond.Signal() //注意这边会有多次被忽略的情况 44 | cond.L.Unlock() 45 | } 46 | }() 47 | // Producer 48 | for { 49 | time.Sleep(time.Second) 50 | cond.L.Lock() 51 | for condition == 3 { 52 | cond.Wait() 53 | } 54 | condition +=3 55 | fmt.Printf("Producer: %d\n", condition) 56 | cond.Signal() 57 | cond.L.Unlock() 58 | } 59 | } 60 | ``` 61 | 62 | ```bash 63 | $ go run main.go 64 | Producer: 3 65 | Consumer: 2 66 | Consumer: 1 67 | Consumer: 0 68 | Producer: 3 69 | Consumer: 2 70 | Consumer: 1 71 | Consumer: 0 72 | ``` 73 | 74 | ## 多播 75 | 76 | ```go 77 | package main 78 | 79 | import ( 80 | "fmt" 81 | ) 82 | var condition = false 83 | 84 | func main() { 85 | m := sync.Mutex{} 86 | c := sync.NewCond(&m) 87 | go func() { 88 | c.L.Lock() 89 | for condition == false { 90 | fmt.Println("goroutine1 wait") 91 | c.Wait() 92 | } 93 | fmt.Println("goroutine1 exit") 94 | c.L.Unlock() 95 | }() 96 | 97 | go func() { 98 | c.L.Lock() 99 | for condition == false { 100 | fmt.Println("goroutine2 wait") 101 | c.Wait() 102 | } 103 | fmt.Println("goroutine2 exit") 104 | c.L.Unlock() 105 | }() 106 | time.Sleep(2e9) 107 | c.L.Lock() 108 | condition = true 109 | c.Broadcast() 110 | c.L.Unlock() 111 | time.Sleep(2e9) 112 | } 113 | ``` 114 | 115 | ```bash 116 | $ go run main.go 117 | goroutine1 wait 118 | goroutine2 wait 119 | broadcast 120 | goroutine1 exit 121 | goroutine2 exit 122 | ``` 123 | 124 | ## 总结 125 | 126 | 本小节届时将条件变量的使用,介绍了单播和多播的使用方式。 -------------------------------------------------------------------------------- /src/go开发环境搭建/helloworld.md: -------------------------------------------------------------------------------- 1 | # 第一个go程序 2 | 3 | go环境已经搭建好了,接下来我们写一下go程序的`hello world` 4 | 5 | 6 | ## Hello World 7 | 8 | go使用go mod 来管理依赖,所以我们先创建一个go语言项目。 9 | 10 | ```bash 11 | $ mkdir helloworld && cd helloworld #创建目录 12 | $ go mod init helloworld #go mod初始化项目,项目名为helloworld 13 | $ touch main.go #创建代码文件 14 | ``` 15 | 16 | 编辑 main.go 17 | ```go 18 | package main 19 | import ( 20 | "fmt" //导入fmt package 21 | ) 22 | 23 | func main() { 24 | fmt.Println("hello world") 25 | } 26 | ``` 27 | 28 | 使用 `go run` 临时编译执行go程序 29 | 30 | ```bash 31 | $ go run main.go 32 | hello world 33 | ``` 34 | 35 | 到此,我们的Go环境基本搭建好了,接下来我们学习过程中需要不断的写练习代码。 -------------------------------------------------------------------------------- /src/go开发环境搭建/环境搭建.md: -------------------------------------------------------------------------------- 1 | # go 环境搭建 2 | 3 | ## golang 安装 4 | 5 | - 获取golang [二进制安装包](https://golang.org/dl/),国内可以选择 [国内站](https://golang.google.cn/dl/) 。 6 | - 选择合适的操作系统和指令平台后,再选择最新版本下载。 7 | 8 | 9 | ## linux、macos 安装golang 10 | 11 | ``` 12 | $ sudo tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz 13 | ``` 14 | 15 | 选择适合你的安装的文件。 例如要在Linux上为64位x86安装Go版本1.12.1,则所需的文件称为go1.12.1.linux-amd64.tar.gz。 16 | 17 | 你可以在/etc/profile(用于系统范围的安装)或$HOME/.profile添加 18 | 19 | ``` 20 | export PATH=$PATH:/usr/local/go/bin 21 | ``` 22 | 来将/usr/local/go/bin添加到PATH环境变量 23 | 24 | 注意:你下次登录计算机之前,对配置文件所做的更改可能不适用。可以使用source $HOME/.profile 来让环境变量立即生效。 25 | 26 | 27 | ## windows 平台安装golang 28 | 29 | Go项目为Windows用户提供了两个安装选项:其一是通过zip文件解压然后在配置环境变量安装,其二是通过MSI安装程序安装,它会自动配置你的环境变量。 30 | 31 | ### 使用MSI安装程序安装 32 | 33 | 打开MSI文件,然后按照提示安装Go工具。默认情况下,安装程序将Go分发放在c:\Go中。 34 | 安装程序应将c:\Go\bin目录放在PATH环境变量中。你可能需要重新启动任何打开的命令提示才能使更改生效。 35 | 36 | ### 使用Zip压缩包安装 37 | 38 | 下载zip文件并将其解压缩到你选择的目录中(我们建议使用c:\​​Go)。 39 | 将Go根目录的bin子目录(例如,c:\Go\bin)添加到PATH环境变量中。 40 | 41 | ### 在Windows下设置环境变量 42 | 43 | 在Windows下,你可以通过“系统”控制面板的“高级”选项卡上的“环境变量”按钮设置环境变量。某些版本的Windows通过“系统”控制面板内的“高级系统设置”选项提供此控制面板。 44 | 45 | 46 | ## 测试安装结果 47 | 48 | 创建 gotest 文件夹,创建test.go 49 | 50 | ```go 51 | package main 52 | 53 | import "fmt" 54 | 55 | func main() { 56 | fmt.Printf("hello, world\n") 57 | } 58 | ``` 59 | 60 | cd 到 gotest目录 61 | 62 | ``` 63 | $ go build -o test test.go 64 | $ ./test 65 | hello, world 66 | ``` 67 | 68 | 如何你看到 "hello, world" 则说明的golang 安装成功了. 69 | 70 | 71 | ## go 环境变量 72 | 73 | 执行 74 | ``` 75 | $ go env 76 | ``` 77 | 主要关注GOPATH、GOBIN 这两个环境变量 78 | 79 | GOPATH :安装的时候默认go path 是你的$HOME/go目录下,你可以通过配置你的GOPATH修改默认的go path。 go path下面你主要关注pkg目录,这个目录会保存你go项目的依赖包。 老版本的golang go build 和 go run 都需要在 go path 下才能运行,新版本(1.12以上) 可以在go path外执行。 80 | 81 | GOBIN:go install编译后的可执行程序存放路径,一般会把这个路径也加入到系统换变量path里。GOBIN目录如果你没有指定,默认可能是空。为空时可执行文件放在GOPATH目录的bin文件夹中。 82 | 83 | 84 | ## 使用golang国内代理 85 | 86 | 由于`墙`的原因,有些golang的第三方库我们不能直接访问,我们需要设置一个代理。 87 | 88 | linux平台 `vim /etc/profile`添加: 89 | 90 | ``` 91 | export GOPROXY=https://goproxy.cn,direct 92 | ``` 93 | 94 | 95 | ## 总结 96 | 97 | 本小节我们介绍了如何安装golang,以及用golang跑了hello world。并且介绍了golang的环境变量,主要关注GOPATH,和GOBIN这两个环境变量。 -------------------------------------------------------------------------------- /src/go开发环境搭建/集成开发工具.md: -------------------------------------------------------------------------------- 1 | # 集成开发工具 2 | 3 | golang的开发ide主要有`vscode`和`Goland`,由于`Goland`是付费产品,本小节我们注重介绍`vscode`golang环境搭建。 4 | 5 | Vscode是微软基于Electron构建的开源编辑器, 是这几年非常流行而且异常强大的编辑器。 6 | 7 | ## 安装vscode 8 | 9 | `vscode`官方下载地址:https://code.visualstudio.com/Download 10 | 11 | ## 安装golang插件 12 | 13 | 首先需要安装go插件 14 | ![](../img/ide1.png) 15 | 16 | 17 | ## 安装golang环境其他工具 18 | 19 | 按`Ctrl+Shift+P`快捷键,输入`go install` 20 | 21 | ![](../img/ide2.png) 22 | 23 | 然后选择`Go:Install/Update Tools`会出来如下弹窗: 24 | 25 | ![](../img/ide3.png) 26 | 27 | 全选安装工具。 28 | 29 | 安装完成后 30 | ![](../img/ide4.png) 31 | 32 | 到此golang开发环境搭建算是完成了。初学者可以安装vscode插件`Code Runer`方便调试运行。 33 | -------------------------------------------------------------------------------- /src/go标准库/README.md: -------------------------------------------------------------------------------- 1 | # go 标准库 2 | 3 | - [strings包](./strings.md) 4 | - [bytes包](./bytes.md) 5 | - [fmt——格式化输入输出](./fmt.md) 6 | - [系统操作](./os.md) 7 | - [time](./time.md) 8 | - [命令行参数解析](./flag.md) 9 | - [json序列化](./json.md) 10 | - [程序日志](./log.md) 11 | - [strconv](./map.md) 12 | - [排序](./sort.md) -------------------------------------------------------------------------------- /src/go标准库/bytes.md: -------------------------------------------------------------------------------- 1 | # bytes包 2 | 3 | `[]byte`字节数组操作是`string`外的另外一种高频操作,在golang中也专门提供了`bytess`包来做这种工作。功能大多数和`strings`类似。 4 | 5 | 6 | ## Compare和Equal方法 7 | 8 | `Compare`方法安装字典顺序比较`a`和`b`,返回值1为a>b,0为a==b,-1为a= x }) 37 | if i < len(a) && a[i] == x { 38 | fmt.Printf("found %d at index %d in %v\n", x, i, a) //found 6 at index 2 in [1 3 6 10 15 21 28 36 45 55] 39 | } 40 | ``` 41 | 42 | ## 自建类型实现`sort.Interface`然后排序 43 | 44 | ```go 45 | type Float32Slice []float32 46 | 47 | func (p Float32Slice) Len() int { return len(p) } 48 | func (p Float32Slice) Less(i, j int) bool { return p[i] < p[j] } 49 | func (p Float32Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 50 | 51 | s := []float32{5.3, 9.2, 6.0, 3.8, 1.1, 4.5} // unsorted 52 | sort.Sort(sort.Reverse(Float32Slice(s))) 53 | fmt.Println(s) //[9.2 6 5.3 4.5 3.8 1.1] 54 | ``` 55 | 56 | 57 | ## `Reverse`可以实现逆序 58 | 59 | ```go 60 | s := []int{5, 2, 6, 3, 1, 4} // unsorted 61 | sort.Sort(sort.Reverse(sort.IntSlice(s))) 62 | fmt.Println(s) //[6 5 4 3 2 1] 63 | ``` 64 | 65 | `sort.Reverse`函数包含了`reverse`结构体他继承排序类型的`sort.Interface`,但是修改了 `Less(i, j int) bool`的方法。 66 | 67 | ```go 68 | func (r reverse) Less(i, j int) bool { 69 | return r.Interface.Less(j, i) 70 | } 71 | ``` -------------------------------------------------------------------------------- /src/go标准库/strconv.md: -------------------------------------------------------------------------------- 1 | # strconv 2 | 3 | `strconv`包提供了字符串和其他golang基础类型的互相转换函数。 4 | 5 | ## 整数和字符串互换 6 | 7 | ```go 8 | i, err := strconv.Atoi("-42") // -42 9 | s := strconv.Itoa(-42) //"-42" 10 | ``` 11 | 12 | ## 布尔类型,浮点数,整数和字符串转换 13 | 14 | ```go 15 | b, err := strconv.ParseBool("true") 16 | f, err := strconv.ParseFloat("3.1415", 64) 17 | i, err := strconv.ParseInt("-42", 10, 64) 18 | u, err := strconv.ParseUint("42", 10, 64) 19 | 20 | s := strconv.FormatBool(true) 21 | s := strconv.FormatFloat(3.1415, 'E', -1, 64) 22 | s := strconv.FormatInt(-42, 16) 23 | s := strconv.FormatUint(42, 16) 24 | ``` 25 | 26 | ## Quote 和 Unquote 27 | 28 | `strconv`包中有对字符串加`"`的方法`Quote`,`Unquote`这是给字符串去`"`. 29 | 30 | ```go 31 | fmt.Println(strconv.Quote(`"Hello 世界"`)) //"\"Hello\t世界\"" 32 | fmt.Println(strconv.QuoteRune('世')) // '世' 33 | fmt.Println(strconv.QuoteRuneToASCII('世')) // '\u4e16' 34 | fmt.Println(strconv.QuoteToASCII(`"Hello 世界"`)) //"\"Hello\t\u4e16\u754c\"" 35 | fmt.Println(strconv.Unquote(`"\"Hello\t世界\""`)) // "Hello 世界" 36 | ``` -------------------------------------------------------------------------------- /src/go标准库/strings.md: -------------------------------------------------------------------------------- 1 | # strings包 2 | 3 | 字符串操作通常是一种高频操作,在golang中专门提供了`strings`包来做这种工作。 4 | 5 | 6 | ## Compare 方法 7 | 8 | ``比较两个字符串的字典序,返回值0表示a==b,-1表示a < b,1表示a > b.通常我们应该运算符==,<,>来比较连个字符串,代码更简介,意思更明细,效率也更高。 9 | 10 | ```go 11 | func Compare(a, b string) int 12 | ``` 13 | 14 | ```go 15 | strings.Compare("a", "b") //-1 16 | strings.Compare("a", "a") //0 17 | strings.Compare("b", "a") //1 18 | ``` 19 | 20 | ## Contains 方法 21 | 22 | `Contains`方法用来判断字符串`s`中是否包含子串`substr` 23 | 24 | ```go 25 | func Contains(s, substr string) bool 26 | ``` 27 | 28 | ```go 29 | strings.Contains("seafood", "foo") //true 30 | ``` 31 | 32 | ## HasPrefix和HasSuffix方法 33 | 34 | `HasPrefix`和`HasSuffix`用来判断字符串中是否包含包含前缀和后缀 35 | 36 | ```go 37 | func HasPrefix(s, prefix string) bool 38 | func HasSuffix(s, suffix string) bool 39 | ``` 40 | 41 | ```go 42 | strings.HasPrefix("Gopher", "Go") //true 43 | strings.HasSuffix("Amigo", "go") //true 44 | ``` 45 | 46 | ## Index 方法和 LastIndex方法 47 | 48 | `Index`返回子串`sep`在字符串`s`中第一次出现的位置,如果找不到,则返回 -1。 49 | `LastIndex`返回子串`sep`在字符串`s`中最后一次出现的位置,如果找不到,则返回 -1。 50 | 51 | ```go 52 | func Index(s, substr string) int 53 | func LastIndex(s, substr string) int 54 | ``` 55 | 56 | ```go 57 | strings.Index("chicken", "ken") //4 58 | strings.Index("chicken", "dmr") //-1 59 | 60 | strings.Index("go gopher", "go") //0 61 | strings.LastIndex("go gopher", "go") //3 62 | strings.LastIndex("go gopher", "rodent") //-1 63 | ``` 64 | 65 | ## Join方法 66 | 67 | `Join`将`a`中的子串连接成一个单独的字符串,子串之间用`sep`拼接 68 | 69 | ```go 70 | func Join(elems []string, sep string) string 71 | ``` 72 | 73 | ```go 74 | s := []string{"foo", "bar", "baz"} 75 | fmt.Println(strings.Join(s, ", ")) //foo, bar, baz 76 | ``` 77 | 78 | ## Repeat方法 79 | 80 | `Repeat`将`count`个字符串`s`连接成一个新的字符串。 81 | 82 | ```go 83 | func Repeat(s string, count int) string 84 | ``` 85 | 86 | ```go 87 | strings.Repeat("na", 2)//nana 88 | ``` 89 | 90 | ## Replace和ReplaceAll方法 91 | 92 | `Replace`和`ReplaceAll`方法为字符串替换方法,`Replace`返回`s`的副本,并将副本中的`old`字符串替换为`new`字符串,替换次数为`n`次,如果`n`为 -1则全部替换。 93 | 94 | ```go 95 | func Replace(s, old, new string, n int) string 96 | func ReplaceAll(s, old, new string) string 97 | ``` 98 | 99 | ```go 100 | strings.Replace("oink oink oink", "k", "ky", 2) //oinky oinky oink 101 | strings.Replace("oink oink oink", "oink", "moo", -1) // moo moo moo 102 | strings.ReplaceAll("oink oink oink", "oink", "moo") // moo moo moo 功能同上 103 | ``` 104 | 105 | ## Split 方法 106 | 107 | `Split`方法以`sep`为分隔符,将`s`切分成多个子串,结果中不包含 sep 本身 108 | 109 | ```go 110 | func Split(s, sep string) []string 111 | ``` 112 | 113 | ```go 114 | strings.Split("a,b,c", ",") //["a" "b" "c"] 115 | strings.Split("a man a plan a canal panama", "a ") //["" "man " "plan " "canal panama"] 116 | strings.Split(" xyz ", "") //[" " "x" "y" "z" " "] 117 | strings.Split("", "Bernardo O'Higgins") //[""] 118 | ``` 119 | 120 | 121 | ## Trim、TrimSpace、TrimPrefix,TrimSuffix方法 122 | 123 | `Trim`将删除`s`首尾连续的包含在`cutset`中的字符 124 | `TrimSpace`将删除`s`首尾连续的的空白字符 125 | `TrimPrefix`删除`s`头部的`prefix`字符串 126 | `TrimSuffix` 删除`s`尾部的`suffix`字符串 127 | 128 | ```go 129 | func Trim(s, cutset string) string 130 | func TrimSpace(s string) string 131 | func TrimPrefix(s, prefix string) string 132 | func TrimSuffix(s, suffix string) string 133 | ``` 134 | 135 | ```go 136 | strings.Trim("¡¡¡Hello, Gophers!!!", "!¡") //Hello, Gophers 137 | strings.TrimSpace(" \t\n Hello, Gophers \n\t\r\n")//Hello, Gophers 138 | 139 | var s = "¡¡¡Hello, Gophers!!!" 140 | strings.TrimPrefix(s, "¡¡¡Hello, ") //Gophers!!! 141 | strings.TrimSuffix(s, ", Gophers!!!") //¡¡¡Hello 142 | ``` 143 | 144 | # 参考资料 145 | 146 | [go strings官方文档](https://golang.org/pkg/strings/) -------------------------------------------------------------------------------- /src/go标准库/time.md: -------------------------------------------------------------------------------- 1 | # time 2 | 3 | `time`包提供了时间的显示和测量用的函数。 4 | 5 | 6 | ## 获取当前时间 7 | 8 | `time`中的`Now`方法会返回当前的时间(`time.Time`)。 9 | ```go 10 | now := time.Now() 11 | fmt.Println(now) //2020-10-23 11:02:53.356985487 +0800 CST m=+0.000042418 12 | 13 | fmt.Println(now.Unix()) //获取时间戳 1603422283 14 | fmt.Println(now.UnixNano())//获取纳秒时间戳 1603422283132651138 15 | 16 | 17 | fmt.Println(now.Year()) //年 18 | fmt.Println(now.Month()) //月 19 | fmt.Println(now.Day()) //日 20 | 21 | fmt.Println(now.Hour()) //时 22 | fmt.Println(now.Minute()) //分 23 | fmt.Println(now.Second()) //秒 24 | fmt.Println(now.Nanosecond()) //纳秒 25 | ``` 26 | 27 | ## 格式化输出 28 | 29 | 使用`time.Time`的`Format`方法格式化时间. 30 | golang的时间格式比较有`特色`。`2006-01-02 15:04:05`代表`年-月-日 小时(24小时制)-分-秒`。 31 | 32 | 33 | ```go 34 | fmt.Println(time.Now().Format("2006-01-02 15:04:05")) //2020-10-23 11:15:41 35 | fmt.Println(time.Now().Format("2006-01-02 15:04:05.000")) //2020-10-23 11:15:41.439 带毫秒 36 | fmt.Println(time.Now().Format("2006-01-02 15:04:05.000000")) //2020-10-23 11:15:41.439274 带微秒 37 | fmt.Println(time.Now().Format("2006-01-02 15:04:05.000000000")) //2020-10-23 11:15:41.439277309 带纳秒 38 | ``` 39 | 40 | ## 解析时间 41 | 42 | 时间戳转时间 43 | 44 | ```go 45 | fmt.Println(time.Unix(1603422283, 0).Format("2006-01-02 15:04:05")) 46 | ``` 47 | 48 | 字符串转时间 49 | 50 | ```go 51 | t, err := time.Parse("2006-01-02 15:04:05", "2020-10-23 11:15:41") 52 | fmt.Println(t) 53 | ``` 54 | 55 | 使用`time.Date`构建时间 56 | 57 | ```go 58 | t := time.Date(2020, 10, 23, 11, 15, 41, 0, time.Local) 59 | fmt.Println(t) 60 | ``` 61 | 62 | ## 时间测量 63 | 64 | 很多时候我们需要比较两个时间,甚至需要测量时间距离。 65 | 66 | 67 | ### 测耗时 68 | 69 | `Time.Sub(t Time)` 方法测量自己和参数中的时间距离 70 | `time.Slice(t Time)`函数测量参数时间t到现在的距离 71 | `time.Until(t Time)`函数测量现在时间到参数t的距离 72 | 73 | ```go 74 | start := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) 75 | end := time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC) 76 | 77 | difference := end.Sub(start) 78 | fmt.Println(difference) //12h0m0s 79 | 80 | fmt.Println(time.Since(start)) //182427h58m21.798590739s 81 | fmt.Println(time.Until(end)) //-182415h58m21.798593974s 82 | ``` 83 | 84 | ### Sleep函数 85 | 86 | `Sleep`函数会让当前的`goroutine`休眠 87 | 88 | ```go 89 | time.Sleep(d Duration) //Duration 是int64的别名代办纳秒值 90 | ``` 91 | 92 | ```go 93 | time.Sleep(1*time.Second) //sleep 1s 94 | time.Sleep(1e9) //sleep 1s 95 | ``` 96 | 97 | 98 | ## 使用`time.After`来处理超时 99 | 100 | ```go 101 | func After(d Duration) <-chan Time 102 | ``` 103 | 104 | ```go 105 | package main 106 | 107 | import ( 108 | "fmt" 109 | "time" 110 | ) 111 | 112 | var c chan int 113 | 114 | func handle(int) {} 115 | 116 | func main() { 117 | select { 118 | case m := <-c: 119 | handle(m) 120 | case <-time.After(10 * time.Second): 121 | fmt.Println("timed out") 122 | } 123 | } 124 | ``` 125 | 126 | ## 定时器Time.Tick 127 | 128 | 使用`Time.Tick`会产生一个定时器. 129 | 130 | ```go 131 | func Tick(d Duration) <-chan Time 132 | ``` 133 | 134 | ```go 135 | package main 136 | 137 | import ( 138 | "fmt" 139 | "time" 140 | ) 141 | 142 | func statusUpdate() string { return "" } 143 | 144 | func main() { 145 | c := time.Tick(5 * time.Second) //每5s 生产时间 往channel 发送 146 | for next := range c { 147 | fmt.Printf("%v %s\n", next, statusUpdate()) 148 | } 149 | } 150 | ``` -------------------------------------------------------------------------------- /src/go标准库/文件读写.md: -------------------------------------------------------------------------------- 1 | # 文件读写 2 | 3 | 在golang的标准库中有三个包可以读写文件`os`,`ioutil`,`bufio`。 4 | `os`是最基础文件操作功能,`ioutil`提供读写小文件的简便功能,`bufio`提供带缓存的区高性能读写功能。 5 | 6 | ## 使用`io`包读取写文件 7 | 8 | ```go 9 | f, err := os.OpenFile("a.txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0755) //文件不存在会创建 写文件会追加在末尾 10 | if err != nil { 11 | log.Fatal(err) 12 | } 13 | f.Write([]byte("aaaa")) 14 | f.WriteString("bbbb") 15 | f.Close() //打开成功的文件句柄 不用的时候一定记得关闭 16 | ff, err := os.OpenFile("a.txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0755) 17 | b := make([]byte, 1024) 18 | n, err := ff.Read(b) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | fmt.Println(string(b[:n])) //aaaabbbb 23 | os.Remove("a.txt") //删除文件 24 | f.Close() 25 | ``` 26 | 27 | ## 使用`ioutil`包读写文件 28 | 29 | `ioutil`包的`ReadAll`和`ReadFile`方法可以读取整个文件的内容到内存,对读取配置文件等小文件非常方便。 30 | 31 | 读文件 32 | 33 | ```go 34 | f, _ := os.Open("aa.txt") 35 | b, _ := ioutil.ReadAll(f) 36 | fmt.Println(string(b)) 37 | f.Close() 38 | 39 | b, _ = ioutil.ReadFile("aa.txt") 40 | fmt.Println(string(b)) 41 | ``` 42 | 43 | 对于大文件读取需要使用`bufio`包。 44 | 45 | 写文件 46 | 47 | ```go 48 | ioutil.WriteFile("aa.txt", []byte("aaaaa\nbbbbb\n"), 0666) 49 | ``` 50 | `ioutil.WriteFile`写文件会覆盖原先所有内容。 51 | 52 | ## `bufio`读写文件 53 | 54 | 使用`bufio`按行读取文件 55 | 56 | ```go 57 | f, _ := os.Open("aa.txt") 58 | defer f.Close() 59 | buf := bufio.NewReader(f) 60 | for { 61 | line, _, err := buf.ReadLine() 62 | if err != nil { 63 | break 64 | } 65 | fmt.Println(string(line)) 66 | } 67 | ``` 68 | 69 | 使用`bufio`写文件 70 | ```go 71 | f, _ := os.OpenFile("aa.txt", os.O_APPEND, 0755) //追加方式写文件 72 | defer f.Close() 73 | w := bufio.NewWriter(f) 74 | w.Write([]byte("aaa\n")) 75 | w.WriteString("bbb\n") 76 | ``` -------------------------------------------------------------------------------- /src/go标准库/标准库概述.md: -------------------------------------------------------------------------------- 1 | # go标准库 2 | 3 | golang语言的流行不仅仅在也语法简单容易和容易入门,还有就是go提供强大的标准库,能满足我们绝大多数的开发任务。 4 | 例如:go标准库包含了`net/http`,使我们用golang开发web服务非常方便。 5 | 6 | 这文档[godoc](https://godoc.org/-/go)包含go所有的标准库名称。 7 | 8 | 标准库涉及范围很广,本小节挑选出我们比较常用的几个做介绍。 9 | -------------------------------------------------------------------------------- /src/go汇编/go汇编使用场景.md: -------------------------------------------------------------------------------- 1 | # Go汇编使用场景 2 | 3 | Go汇编在实际开发中有其特定的应用场景,本文将详细介绍几个主要的使用场景,并提供具体的示例说明。 4 | 5 | ## 性能优化场景 6 | 7 | ### 1. SIMD指令优化 8 | 9 | 在进行向量计算、图像处理等场景时,使用CPU的SIMD(单指令多数据)指令集可以显著提升性能。Go汇编允许我们直接使用这些指令。 10 | 11 | 示例:使用AVX2指令集优化向量加法 12 | 13 | ```go 14 | // vector_add.go 15 | package simd 16 | 17 | //go:noescape 18 | func AddVectors(a, b, result []float64) 19 | ``` 20 | 21 | ```asm 22 | // vector_add_amd64.s 23 | #include "textflag.h" 24 | 25 | // func AddVectors(a, b, result []float64) 26 | TEXT ·AddVectors(SB), NOSPLIT, $0 27 | MOVQ a+0(FP), SI // a slice 28 | MOVQ b+24(FP), BX // b slice 29 | MOVQ result+48(FP), DI // result slice 30 | MOVQ a_len+8(FP), CX // length of slice 31 | 32 | SHRQ $2, CX // CX /= 4 33 | 34 | loop: 35 | VMOVUPD (SI), Y0 // 加载4个float64到Y0 36 | VMOVUPD (BX), Y1 // 加载4个float64到Y1 37 | VADDPD Y1, Y0, Y2 // 并行加法 38 | VMOVUPD Y2, (DI) // 存储结果 39 | 40 | ADDQ $32, SI // 更新指针 41 | ADDQ $32, BX 42 | ADDQ $32, DI 43 | DECQ CX 44 | JNZ loop 45 | 46 | RET 47 | ``` 48 | 49 | ### 2. 密集计算优化 50 | 51 | 对于一些计算密集型的操作,如加密算法、哈希计算等,使用汇编可以获得更好的性能。 52 | 53 | ## 系统调用实现 54 | 55 | ### 1. 直接系统调用 56 | 57 | 在需要直接访问操作系统功能时,使用汇编可以避免额外的运行时开销。 58 | 59 | 示例:实现一个简单的系统调用 60 | 61 | ```go 62 | // syscall.go 63 | package syscall 64 | 65 | //go:noescape 66 | func Syscall(trap uintptr, a1, a2, a3 uintptr) (r1, r2, err uintptr) 67 | ``` 68 | 69 | ```asm 70 | // syscall_amd64.s 71 | #include "textflag.h" 72 | 73 | TEXT ·Syscall(SB), NOSPLIT, $0 74 | MOVQ trap+0(FP), AX // 系统调用号 75 | MOVQ a1+8(FP), DI // 第一个参数 76 | MOVQ a2+16(FP), SI // 第二个参数 77 | MOVQ a3+24(FP), DX // 第三个参数 78 | SYSCALL // 执行系统调用 79 | 80 | MOVQ AX, r1+32(FP) // 返回值1 81 | MOVQ DX, r2+40(FP) // 返回值2 82 | MOVQ CX, err+48(FP) // 错误码 83 | RET 84 | ``` 85 | 86 | ## 硬件交互 87 | 88 | ### 1. 特殊指令访问 89 | 90 | 在需要访问特殊的CPU指令或硬件功能时,Go汇编是必不可少的工具。 91 | 92 | 示例:读取CPU的时间戳计数器(TSC) 93 | 94 | ```go 95 | // tsc.go 96 | package hardware 97 | 98 | //go:noescape 99 | func ReadTSC() uint64 100 | ``` 101 | 102 | ```asm 103 | // tsc_amd64.s 104 | #include "textflag.h" 105 | 106 | TEXT ·ReadTSC(SB), NOSPLIT, $0 107 | RDTSC // 读取TSC 108 | SHLQ $32, DX // 高32位左移 109 | ADDQ DX, AX // 组合结果 110 | MOVQ AX, ret+0(FP) // 返回值 111 | RET 112 | ``` 113 | 114 | ## 性能对比 115 | 116 | 以向量加法为例,对比Go原生实现和汇编实现的性能差异: 117 | 118 | ```go 119 | // 原生Go实现 120 | func AddVectorsGo(a, b, result []float64) { 121 | for i := 0; i < len(a); i++ { 122 | result[i] = a[i] + b[i] 123 | } 124 | } 125 | ``` 126 | 127 | 在处理大量数据时,使用SIMD指令的汇编实现可以获得2-4倍的性能提升。 128 | 129 | ## 使用建议 130 | 131 | 1. **谨慎使用**:Go汇编应该在确实需要性能优化的关键路径上使用 132 | 2. **维护成本**:汇编代码的可读性和可维护性较差,需要详细的文档说明 133 | 3. **平台兼容**:注意汇编代码的平台依赖性,需要为不同架构提供实现 134 | 4. **性能验证**:使用基准测试验证性能提升效果,确保优化的价值 135 | 136 | ## 实际项目案例 137 | 138 | 1. **标准库中的使用**: 139 | - `crypto`包中的哈希函数实现 140 | - `runtime`包中的调度器和内存分配器 141 | - `math`包中的一些数学函数 142 | 143 | 2. **开源项目中的应用**: 144 | - 高性能JSON解析器 145 | - 加密库 146 | - 图像处理库 147 | 148 | ## 总结 149 | 150 | Go汇编是一个强大的工具,但应该在合适的场景下使用。主要应用场景包括: 151 | 152 | 1. 性能关键的计算密集型操作 153 | 2. 需要使用特殊CPU指令的场景 154 | 3. 系统级编程需求 155 | 4. 硬件直接交互 156 | 157 | 在使用Go汇编时,需要权衡开发维护成本和性能提升收益,确保其使用是必要且合理的。 -------------------------------------------------------------------------------- /src/go汇编/go汇编基础.md: -------------------------------------------------------------------------------- 1 | # Go汇编基础 2 | 3 | ## Plan9汇编基本概念 4 | 5 | Go汇编是基于Plan9汇编改编而来,它是一种独特的汇编方言。Go汇编的设计目标是提供一种跨平台的汇编语言抽象,使得同一段汇编代码可以在不同的硬件架构上运行 1。 6 | 7 | ### 虚拟寄存器 8 | 9 | Go汇编中引入了四个重要的虚拟寄存器 2: 10 | 11 | 1. FP (Frame pointer):用于访问函数的参数和局部变量 12 | 2. PC (Program counter):程序计数器,用于跳转和分支 13 | 3. SB (Static base pointer):用于引用全局符号 14 | 4. SP (Stack pointer):指向栈顶 15 | 16 | ### 操作数和指令 17 | 18 | 在Go汇编中,操作数的方向与Intel汇编相反,是从左到右的。主要的指令类型包括 2: 19 | 20 | - 数据移动指令: 21 | - MOVB:移动1字节 22 | - MOVW:移动2字节 23 | - MOVD:移动4字节 24 | - MOVQ:移动8字节 25 | 26 | - 运算指令: 27 | - ADD:加法运算 28 | - SUB:减法运算 29 | - IMUL:乘法运算 30 | 31 | - 栈操作: 32 | - 不使用PUSH/POP,而是通过SUB/ADD指令操作SP实现 33 | 34 | ## 内存布局 35 | 36 | ### 数据定义 37 | 38 | Go汇编中使用DATA和GLOBL指令来定义变量 1: 39 | 40 | ```asm 41 | // 定义全局变量 42 | DATA symbol+offset(SB)/width, value 43 | GLOBL symbol(SB), NOPTR, $size 44 | ``` 45 | 46 | ### 函数声明 47 | 48 | 函数声明的基本格式 3: 49 | 50 | ```asm 51 | TEXT pkgname·funcname(SB), NOSPLIT, $framesize-argsize 52 | ``` 53 | 54 | - pkgname:包名 55 | - funcname:函数名 56 | - NOSPLIT:不进行栈分裂检查 57 | - framesize:栈帧大小 58 | - argsize:参数和返回值的大小 59 | 60 | ## 寄存器使用 61 | 62 | 除了虚拟寄存器外,Go汇编还可以使用目标平台的物理寄存器。在AMD64架构下,常用的通用寄存器包括: 63 | 64 | - RAX, RBX, RCX, RDX:通用数据寄存器 65 | - RSI, RDI:源索引和目标索引寄存器 66 | - R8-R15:额外的通用寄存器 67 | 68 | ## 注意事项 69 | 70 | 1. Go汇编不能独立使用,必须与Go源码文件一起编译 71 | 2. 在编写汇编代码时,需要特别注意栈的管理和内存对齐 72 | 3. 使用Go汇编时,应该重点关注函数调用中的参数传递和栈帧布局 73 | 4. 编写汇编代码时应遵循Go的调用约定和ABI规范 74 | 75 | ## 调试技巧 76 | 77 | 可以使用以下命令查看Go代码对应的汇编代码: 78 | 79 | ```bash 80 | go build -gcflags "-S" main.go # 查看汇编代码 81 | go tool compile -S main.go # 查看优化后的汇编代码 82 | ``` -------------------------------------------------------------------------------- /src/go汇编/go汇编笔记.md: -------------------------------------------------------------------------------- 1 | # go 汇编笔记 2 | 3 | 4 | # JLS 指令 5 | ```assembly 6 | CMPQ CX,$3 7 | JLS 48 8 | ``` 9 | 10 | JLS(通JBE一个意思)转移条件:`CMPQ CX,$3 ;JLS 48` 当CX的值小于或等于3的时候转跳到48。 11 | 12 | 13 | # 编译器优化的例子 14 | 15 | go标准库`binary.BigEndian`中的`bigEndian.PutUint32` 16 | ```golang 17 | func PutUint32(b []byte, v uint32) { 18 | _ = b[3] // early bounds check to guarantee safety of writes below 19 | b[0] = byte(v >> 24) 20 | b[1] = byte(v >> 16) 21 | b[2] = byte(v >> 8) 22 | b[3] = byte(v) 23 | } 24 | ``` 25 | 26 | 这个例子 _ = b[3] 这个语句对于的汇编是 27 | ```bash 28 | $ go tool compile -S main.go |grep main.go:10 29 | 0x000e 00014 (main.go:10) PCDATA $0, $0 30 | 0x000e 00014 (main.go:10) PCDATA $1, $0 31 | 0x000e 00014 (main.go:10) MOVQ "".b+40(SP), CX 32 | 0x0013 00019 (main.go:10) CMPQ CX, $3 33 | 0x0017 00023 (main.go:10) JLS 48 34 | 0x0030 00048 (main.go:10) MOVL $3, AX 35 | 0x0035 00053 (main.go:10) CALL runtime.panicIndex(SB) 36 | 0x003a 00058 (main.go:10) XCHGL AX, AX 37 | ``` 38 | 意思很明显是提前判断一下是不是slice 下标越界。 39 | 但是这个 为什么不直接写成如下的样子,不是也会检查 panic,而不需要额外的 `_ = b[3]` 40 | ```golang 41 | func PutUint32(b []byte, v uint32) { 42 | b[3] = byte(v) 43 | b[2] = byte(v >> 8) 44 | b[1] = byte(v >> 16) 45 | b[0] = byte(v >> 24) 46 | } 47 | ``` 48 | 49 | 其实这边就是涉及编译器优化的问题,我们看下原先 `PutUint32`的汇编去掉gc的代码 50 | 51 | ``` 52 | "".PutUint32 STEXT nosplit size=59 args=0x20 locals=0x18 53 | 0x0000 00000 (main.go:9) TEXT "".PutUint32(SB), NOSPLIT|ABIInternal, $24-32 54 | 0x0000 00000 (main.go:9) SUBQ $24, SP 55 | 0x0004 00004 (main.go:9) MOVQ BP, 16(SP) 56 | 0x0009 00009 (main.go:9) LEAQ 16(SP), BP 57 | 0x000e 00014 (main.go:10) MOVQ "".b+40(SP), CX //这边是回去slice len的值 58 | 0x0013 00019 (main.go:10) CMPQ CX, $3 59 | 0x0017 00023 (main.go:10) JLS 48 60 | 0x0019 00025 (main.go:11) MOVL "".v+56(SP), AX 61 | 0x001d 00029 (main.go:11) BSWAPL AX 62 | 0x001f 00031 (main.go:14) MOVQ "".b+32(SP), CX 63 | 0x0024 00036 (main.go:14) MOVL AX, (CX) 64 | 0x0026 00038 (main.go:15) MOVQ 16(SP), BP 65 | 0x002b 00043 (main.go:15) ADDQ $24, SP 66 | 0x002f 00047 (main.go:15) RET 67 | 0x0030 00048 (main.go:10) MOVL $3, AX 68 | 0x0035 00053 (main.go:10) CALL runtime.panicIndex(SB) 69 | 0x003a 00058 (main.go:10) XCHGL AX, AX 70 | 71 | ``` 72 | 从这段汇编来看,你会看到 73 | ```golang 74 | b[3] = byte(v) 75 | b[2] = byte(v >> 8) 76 | b[1] = byte(v >> 16) 77 | b[0] = byte(v >> 24) 78 | ``` 79 | 这个已经直接被优化掉 80 | ```assembly 81 | MOVL "".v+56(SP), AX 82 | BSWAPL AX //指令作用是:32位寄存器内的字节次序变反。比如:(EAX)=9668 8368H,执行指令:BSWAP EAX ,则(EAX)=6883 6896H。 83 | ``` 84 | BSWAPL 指令做的工作就和上4个做的工作一样。 85 | 86 | 我们再看看乱序后的汇编代码 87 | 88 | ```bash 89 | "".PutUint32 STEXT nosplit size=79 args=0x20 locals=0x18 90 | 0x0000 00000 (main.go:9) TEXT "".PutUint32(SB), NOSPLIT|ABIInternal, $24-32 91 | 0x0000 00000 (main.go:9) SUBQ $24, SP 92 | 0x0004 00004 (main.go:9) MOVQ BP, 16(SP) 93 | 0x0009 00009 (main.go:9) LEAQ 16(SP), BP 94 | 0x000e 00014 (main.go:10) MOVQ "".b+40(SP), CX 95 | 0x0013 00019 (main.go:10) CMPQ CX, $3 96 | 0x0017 00023 (main.go:10) JLS 68 97 | 0x0019 00025 (main.go:11) MOVL "".v+56(SP), AX 98 | 0x001d 00029 (main.go:11) MOVQ "".b+32(SP), CX 99 | 0x0022 00034 (main.go:11) MOVB AL, 3(CX) 100 | 0x0025 00037 (main.go:12) MOVL AX, DX 101 | 0x0027 00039 (main.go:12) SHRL $8, AX 102 | 0x002a 00042 (main.go:12) MOVB AL, 2(CX) 103 | 0x002d 00045 (main.go:13) MOVL DX, AX 104 | 0x002f 00047 (main.go:13) SHRL $16, DX 105 | 0x0032 00050 (main.go:13) MOVB DL, 1(CX) 106 | 0x0035 00053 (main.go:14) SHRL $24, AX 107 | 0x0038 00056 (main.go:14) MOVB AL, (CX) 108 | 0x003a 00058 (main.go:15) MOVQ 16(SP), BP 109 | 0x003f 00063 (main.go:15) ADDQ $24, SP 110 | 0x0043 00067 (main.go:15) RET 111 | 0x0044 00068 (main.go:10) MOVL $3, AX 112 | 0x0049 00073 (main.go:10) CALL runtime.panicIndex(SB) 113 | 0x004e 00078 (main.go:10) XCHGL AX, AX 114 | ``` 115 | 明显看出来,乱序后没有编译器指令优化。 116 | 117 | 从这里我们就可以知道为什么要先写`_ = b[3]`这样语句判断下下标边界,而后续的四行代码被编译器优化后 只有对 `v` 参数做`BSWAPL`也就没有检查边界的地方。 -------------------------------------------------------------------------------- /src/go汇编/go汇编简介.md: -------------------------------------------------------------------------------- 1 | # go汇编简介 2 | 3 | ## 为什么要学习go汇编 4 | 5 | go 虽然刚过了10周年的生日,但是go的编译器编译的汇编指令优化程度不是很高,无法和c/c++生态相比。在一些高频使用的算法和函数,go原生有时候显得比较无力。go标准库里头特别是算法库和runtime都大量使用go汇编。 6 | 我们在写向量计算的相关代码时,如果利用cpu的sse和avx的指令集会带来可观的性能提升。 7 | 我们在排查问题或者了解go底层代码是如何运行的,那么go汇编回事一个利器。汇编代码面前一切了无秘密。 8 | 9 | ## go汇编 10 | 11 | go汇编和不同于inter汇编和AT&T汇编,go汇编源于plan9汇编,plan9汇编的相关知识感兴趣的可以去了解下,这里不多做介绍。go汇编是go语言的一部分,只能和go语言源码文件一起编译使用,不像inter汇编和AT&T汇编可以单独编译运行。 12 | 13 | 14 | ## CPU 通用寄存器 15 | 16 | 通用寄存器的名字在 X64 和 plan9 中的对应关系: 17 | 18 | 19 | | X64 | rax | rbx| rcx | rdx | rdi | rsi | rbp | rsp | r8 | r9 | r10 | r11 | r12 | r13 | r14 | rip| 20 | |--|--|--|--| --| --|--| --|--|--|--|--|--|--|--|--|--| 21 | | Plan9 | AX | BX | CX | DX | DI | SI | BP | SP | R8 | R9 | R10 | R11 | R12 | R13 | R14 | PC | 22 | 23 | ## 一个程序的内存布局 24 | 25 | ![](../img/asm1.png) 26 | 27 | - 代码段:存储程序指令,位于内存最低端 28 | - 数据段(初始化数据段):全局变量或者静态变量,数据段分只读区和读写区。 29 | - BSS段(未初始化数据段):未初始化全局变量 30 | - 栈:一种LIFO结构,从高地址向低地址增长。 31 | - 堆:动态分布内存区,从低向高增长。 32 | 33 | 34 | # 参考资料 35 | 36 | - [golang-asm](https://lrita.github.io/2017/12/12/golang-asm) 37 | - [A Manual for the Plan 9 assembler](https://9p.io/sys/doc/asm.html) -------------------------------------------------------------------------------- /src/go汇编/go汇编运用.md: -------------------------------------------------------------------------------- 1 | # 看一下有趣的汇编运用例子 2 | 3 | ```go 4 | package main 5 | 6 | import "fmt" 7 | 8 | const s = "Go101.org" 9 | //len(s)==1 10 | //1 << 9 == 512 11 | // 512/128 =4 12 | var a byte = 1 << len(s) / 128 13 | var b byte = 1 << len(s[:]) / 128 14 | 15 | func main() { 16 | fmt.Println(a, b) 17 | } 18 | ``` 19 | 20 | ```bash 21 | $ go run main.go 22 | 4 0 23 | ``` 24 | 25 | 为什么结果会不一样?建议读者运行下这个程序,不着急看下面的结果. 26 | 27 | 如果你不懂go汇编,你很难知道到底发生了什么? 你懂go汇编的话,你看容易在汇编代码中找到端倪. 28 | 29 | ```bash 30 | go tool compile -l -S -N main.go 31 | ``` 32 | 33 | 在汇编代码中看到 34 | ``` 35 | "".a SNOPTRDATA size=1 36 | 0x0000 04 . 37 | "".b SNOPTRBSS size=1 38 | ``` 39 | 40 | `var a byte = 1 << len(s) / 128` 这段代码被go编译器(go 1.14)直接优化成a初始化为4的变量,b这没被初始化,b的赋值在main package的init函数中 41 | 42 | ``` 43 | 0x0015 00021 (main.go:8) MOVQ AX, ""..autotmp_0+8(SP) 44 | 0x001a 00026 (main.go:8) MOVQ $9, ""..autotmp_0+16(SP) 45 | 0x0023 00035 (main.go:8) MOVQ $9, ""..autotmp_1(SP) 46 | 0x002b 00043 (main.go:8) JMP 45 47 | 0x002d 00045 (main.go:8) MOVB $0, "".b(SB) 48 | ``` 49 | 50 | 上面的代码很有趣,b被赋值为0,至于0从哪里冒出来的,完全看不到. 51 | 我们很有理由怀疑go的编译器出bug了,bug不是因为b的结果不对,而是应该对len(s)和len(s[:])用同一套规则,假定 `1 << len(s[:])`已经 让byte类型溢出了其结果为0,那么应该`1 << len(s) `这个也应该是一样的结果.但是编译器s是常量s[:]是变量,len(s)还是常量,len(s[:])是变量,常量在运算过程中有隐式类型转换,`1 << len(s)`1会变成int类型`var a byte = int(1) << len(s) / 128`,变量则没有,所以b为`uint8(1) << len(s[:])`的结果为0. 52 | 53 | 54 | ## 参考资料 55 | 56 | [go100and1](https://twitter.com/go100and1/status/1309188138015760385) -------------------------------------------------------------------------------- /src/go汇编/函数.md: -------------------------------------------------------------------------------- 1 | # Go汇编中的函数 2 | 3 | ## 函数调用约定 4 | 5 | Go汇编中的函数调用遵循特定的约定 1: 6 | 7 | ### 函数声明 8 | 9 | ```asm 10 | TEXT pkgname·funcname(SB), [flags], $framesize-argsize 11 | ``` 12 | 13 | - pkgname:包名 14 | - funcname:函数名 15 | - flags:函数标志(如NOSPLIT表示不进行栈分裂检查) 16 | - framesize:栈帧大小 17 | - argsize:参数和返回值的总大小 18 | 19 | ## 参数传递 20 | 21 | Go语言中的参数传递完全通过栈来实现 2。函数调用时的栈布局如下: 22 | 23 | 1. 调用参数和返回值空间 24 | 2. 返回地址 25 | 3. 局部变量空间 26 | 4. 调用其他函数的参数空间 27 | 28 | ### 参数访问 29 | 30 | - 通过FP伪寄存器访问函数参数 31 | - 参数从低地址到高地址排列 32 | - 第一个参数位于0(FP) 33 | - 后续参数根据类型大小顺序偏移 34 | 35 | ## 栈帧结构 36 | 37 | ### 栈帧布局 38 | 39 | 栈帧是向下增长的,主要包含以下部分 4: 40 | 41 | 1. 函数参数和返回值区域 42 | 2. 保存的BP寄存器值 43 | 3. 局部变量区域 44 | 4. 临时存储区域 45 | 46 | ### 栈操作指令 47 | 48 | ```asm 49 | SUBQ $framesize, SP // 分配栈帧空间 50 | MOVQ BP, (SP) // 保存BP 51 | LEAQ (SP), BP // 设置新的BP 52 | 53 | // 函数返回前 54 | MOVQ (SP), BP // 恢复BP 55 | ADDQ $framesize, SP // 释放栈帧空间 56 | ``` 57 | 58 | ## 函数类型 59 | 60 | Go中有四种主要的函数类型 2: 61 | 62 | 1. 顶层函数(top-level func) 63 | 2. 值接收者方法(method with value receiver) 64 | 3. 指针接收者方法(method with pointer receiver) 65 | 4. 函数字面量(func literal,包括闭包) 66 | 67 | ## 调用规范 68 | 69 | ### 函数调用过程 70 | 71 | 1. 准备参数和返回值空间 72 | 2. CALL指令调用函数 73 | 3. 被调用函数分配栈帧 74 | 4. 执行函数体 75 | 5. 设置返回值 76 | 6. RET指令返回 77 | 78 | ### 寄存器使用 79 | 80 | - BP:保存调用者的栈基址 81 | - SP:指向当前栈顶 82 | - PC:程序计数器,用于指令跳转 83 | 84 | ## 栈分裂 85 | 86 | 为了处理栈溢出,Go使用栈分裂机制 5: 87 | 88 | ```asm 89 | MOVQ (TLS), CX // 获取当前线程的g结构体 90 | CMPQ SP, 16(CX) // 检查是否需要栈扩展 91 | JLS stack_split // 需要扩展则跳转处理 92 | ``` 93 | 94 | ## 实践建议 95 | 96 | 1. 使用NOSPLIT标志避免栈分裂检查 97 | 2. 注意参数和返回值的对齐要求 98 | 3. 正确维护BP寄存器 99 | 4. 确保栈平衡(分配和释放配对) -------------------------------------------------------------------------------- /src/go语言基础/README.md: -------------------------------------------------------------------------------- 1 | # go 语言基础 2 | 3 | - [第一个go程序](./第一个go程序.md) 4 | - [常量](./常量.md) 5 | - [变量](./变量.md) 6 | - [基本类型](./基本类型.md) 7 | - [字符串](./string.md) 8 | - [数组和切片](./数组和切片.md) 9 | - [map](./map.md) 10 | - [指针类型](./指针类型.md) 11 | - [运算](./运算.md) 12 | - [流程控制](./流程控制.md) 13 | - [函数](./函数.md) 14 | - [结构体和方法](./结构体和方法.md) 15 | - [package和可见性](./package和可见性.md) 16 | - [goroutine和channel](./goroutine和channel.md) 17 | - [interface](./interface.md) 18 | - [反射](./反射.md) 19 | - [错误处理](./错误处理.md) 20 | - [panic和recover](./panic和recover.md) -------------------------------------------------------------------------------- /src/go语言基础/goroutine和channel.md: -------------------------------------------------------------------------------- 1 | # goroutine和channel 2 | 3 | 4 | ## goroutine 5 | 6 | golang原生支持并发,在golang中每一个并发单元叫`goroutine`。`goroutine`你可以理解为golang实现轻量级的用户态线程。go程序启动的时候 7 | 其主函数就开始在一个单独的goroutine中运行,这个goroutine我们叫main goroutine。 8 | 9 | 在golang中启动一个goroutine的成本很低,通常只需要在普通函数执行前加`go`关键字就可以。 10 | 11 | ```go 12 | func fn(){ 13 | fmt.Println("hello world") 14 | } 15 | 16 | fn() 17 | go fn() //启用新的goroutine中执行 fn函数 18 | ``` 19 | 20 | 21 | ## channel 22 | 23 | channel是golang中goroutine通讯的一直机制,每一个channel都带有类型,channel使用`make`来创建。 24 | 25 | ```go 26 | ch1 :=make(chan int) //创建不带缓冲区的channel 27 | ch2 :=make(chan int,10)//创建带缓冲区的channel 28 | ``` 29 | 30 | channel是指针类型,它的初始值为`nil`,channel的操作主要有 发送,接受,和关闭。 31 | 32 | ```go 33 | ch <- x // 发送x 34 | x := <-ch // 从channel中接收 然后赋值给x 35 | <-ch // 接收后丢弃 36 | close(x) // 37 | ``` 38 | 39 | 不带缓存的`channel`,发送的时候会阻塞当前`goroutine`,知道`channel`的信息被其他`goroutine`消费。 40 | 带缓存的`channel`,当这个channel的缓存队列没有满是往`channel`写数据是不会阻塞的,当队列满是会阻塞这个`goroutine`。 41 | `channel`读取的时候如果`channel`队列有值会读取,队列为空的时候会塞这个`goroutine`直到`channel`有值可以读取。 42 | 当一个`channel`被close后,基于该channel的发送操作都将导致`panic`,接收操作可以接受到已经channel队列里头数据,channel队列为空时产生一个零值的数据。 43 | 44 | 45 | golang中的`channel`还可以带方向。 46 | 47 | ```go 48 | var out chan<- int //只发送,不能接收 49 | var in <-chan int //只接收,不能发送 注意 对只接收的channel close会引起编译错误 50 | ``` 51 | 52 | 53 | ## select控制结构和channel 54 | 55 | `select`是golang中的一个控制结构,和switch表达式有点相似,不同的是`select`的每个`case`分支必须是一个通信操作(发送或者接收)。 56 | `select`随机选择可执行`case`分支。如果没有`case`分支可执行,它将阻塞,直到有`case`分支可执行。 57 | 带`default`分支的`select`在没有可执行`case`分支时会执行`default`。 58 | 59 | ```go 60 | ch :=make(chan int,1) 61 | select { 62 | case ch <- 1 : 63 | //代码 64 | case n:= <-ch : 65 | //代码 66 | default : //default 是可选的 67 | //代码 68 | } 69 | ``` 70 | 71 | `select`语句的特性: 72 | - 每个`case`分支都是通信操作 73 | - 所有`case分支`表达式都会被求值 74 | - 如果任意某个`case分支`不阻塞,它就执行,其他被忽略。 75 | - 如果有多个`case`分支都不阻塞,`select`会随机地选出一个执行。其他不会执行。 76 | 否则: 77 | 1. 如果有 `default` ,则执行该语句。 78 | 2. 如果没有 `default`,`select` 将阻塞,直到某个`case分支`可以运行; 79 | 80 | 81 | ```go 82 | ch := make(chan int, 1) //这边需要使用1个缓冲区,这样子可以在一个goroutine内使用 83 | 84 | for { 85 | select { 86 | case ch <- 1: 87 | fmt.Println("send") 88 | case n := <-ch: 89 | fmt.Println(n) 90 | default: 91 | fmt.Println("dd") 92 | } 93 | time.Sleep(1e9) 94 | } 95 | ``` 96 | 97 | 本小节简介了`goroutine`和`channel`相关概念,具体的并发编程模型将在下个章节详细探讨。 -------------------------------------------------------------------------------- /src/go语言基础/go语言基础.md: -------------------------------------------------------------------------------- 1 | # Go语言基础 2 | 3 | ## go的25个语言关键字 4 | 5 | ``` 6 | break default func interface select 7 | case defer go map struct 8 | chan else goto package switch 9 | const fallthrough if range type 10 | continue for import return var 11 | ``` 12 | 13 | 这个25个关键字,不能自定义使用。变量和函数应避免和上面的25个重名。 14 | 15 | ## 预定的36个符号 16 | 17 | ### 内建常量 18 | 19 | true false iota nil 20 | 21 | ### 内建类型 22 | 23 | int int8 int16 int32 int64 24 | uint uint8 uint16 uint32 uint64 uintptr 25 | float32 float64 complex128 complex64 26 | bool byte rune string error 27 | 28 | ### 内建函数 29 | 30 | make len cap new append copy close delete 31 | complex real imag 32 | panic recover 33 | 34 | 35 | ## go语言命名规范 36 | 37 | Go程序员应该使用驼峰命名,当名字由几个单词组成时使用大小写分隔,而不是用下划线分隔。例如“NewObject”或者“newObject”,而非“new_object”. 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/go语言基础/interface.md: -------------------------------------------------------------------------------- 1 | # 接口(interface) 2 | 3 | golang中接口(interface)是被非常精心设计的,利用接口golang可以实现很多类似面向对象的设计,而且比传统的面向对象更加方便。 4 | 5 | 接口是一组方法的定义的集合,接口中没有变量。 6 | 7 | 接口的格式: 8 | 9 | ```go 10 | type 接口名 interface { 11 | M1(参数列表) (返回值列表) //方法1 12 | M2(参数列表) (返回值列表) //方法2 13 | ... 14 | } 15 | ``` 16 | 17 | 某个类型`全部实现接口中定义的方法`,我们称作该类型实现了该接口,而不需要在代码中显示声明该类型实现某个接口。 18 | 19 | ```go 20 | type Sayer interface{ 21 | Say() 22 | } 23 | 24 | type Dog struct{} 25 | type Cat struct{} 26 | 27 | func (d *Dog) Say() { 28 | fmt.Println("wang ~") 29 | } 30 | 31 | func (Cat *Cat) Say() { 32 | fmt.Println("miao ~") 33 | } 34 | 35 | func Say(s Sayer) { //函数使用接口类型做参数 36 | s.Say() 37 | } 38 | 39 | var sayer Sayer 40 | 41 | dog:=new(Dog) 42 | cat:=new(Cat) 43 | 44 | sayer = dog //这样赋值给接口变量是ok的,Dog实现了Say方法 45 | sayer.Say() 46 | 47 | sayer = cat 48 | sayer.Say() 49 | 50 | Say(dog) //这样子传参也ok 51 | Say(cat) 52 | ``` 53 | 54 | 从上面的例子上看,golang的`interface`可以实现面向对象语言多态的特性,而且更加简洁,高效。 55 | 56 | 57 | ## 接口嵌套 58 | 59 | golang的接口是可以嵌套的,一个接口可以嵌套一个或者多个其他接口。 60 | 61 | ```go 62 | 63 | type Reader interface{ 64 | Read() 65 | } 66 | 67 | type Writer interface { 68 | Write() 69 | } 70 | 71 | 72 | type ReadWrite interface { //ReadWrite 嵌套了 Reader 和 Writer 73 | Reader 74 | Writer 75 | } 76 | ``` 77 | 78 | ## 空接口 79 | 80 | 在接口中,空接口有其独特的位置,空接口没有定义任何方法,那么在golang中任何类型都实现了这个接口。 81 | 82 | ```go 83 | interface{} 84 | ``` 85 | 86 | 我们再看下`fmt`包中`Println`的定义: 87 | ```go 88 | //func Println(a ...interface{}) (n int, err error) 89 | fmt.Println(1,"aa",true) //由于参数是 interface{} 所以可以传任意类型 90 | ``` 91 | 92 | ## 类型断言 93 | 94 | 接口类型的变量支持类型断言,通过类型断言我们可以检测接口类型变量底层真实的类型。 95 | 96 | 类型断言表达式如下: 97 | 98 | ```go 99 | v,ok := varInterface.(T) 100 | ``` 101 | 102 | 103 | ```go 104 | var i interface{} 105 | a := 1 106 | i = a 107 | 108 | if temp, ok := i.(int); ok { //实际开发中我们经常会这样子 做类型转换 109 | fmt.Println(temp) 110 | } 111 | 112 | 113 | dog:=new(Dog) 114 | _,ok := dog.(Sayer) //我们还可以判某个类似是否实现了某个接口 115 | ``` 116 | 117 | 我们可以使用`switch case`语句来更多规模的类型检测 118 | 119 | 比如`fmt`包中`printArg`的这一段代码 120 | 121 | ```go 122 | switch f := arg.(type) { 123 | case bool: 124 | p.fmtBool(f, verb) 125 | case float32: 126 | p.fmtFloat(float64(f), 32, verb) 127 | case float64: 128 | p.fmtFloat(f, 64, verb) 129 | case complex64: 130 | p.fmtComplex(complex128(f), 64, verb) 131 | ... 132 | ``` -------------------------------------------------------------------------------- /src/go语言基础/map.md: -------------------------------------------------------------------------------- 1 | # Map 2 | 3 | map结果通常是现代语言中高频使用的结构。了解map的使用方法和map的底层解构是十分必要的,本小节将介绍map的使用, 4 | map的底层原来健在go runtime的章节介绍。 5 | 6 | ## Map 变量声明 7 | 8 | ```go 9 | var m map[keytype]valuetype 10 | ``` 11 | map的key是有要求的,key必须是可比较类型,比如常见的 `string`,`int`类型。而数组,切片,和结构体不能作为key类型。 12 | 13 | map的value可以是任何类型。 14 | 15 | map的默认初始值为 `nil` 16 | 17 | 18 | ## 创建map和添加元素 19 | 20 | golang中创建`map`需要使用`make`方法 21 | 22 | 格式如下 23 | 24 | ```go 25 | m := make(map[keytype]valuetype, cap) 26 | ``` 27 | 28 | `cap`参数可以省略。map的容量是根据实际需要自动伸缩。 29 | 30 | 31 | 内置函数`len`可以获取map的长度 32 | 33 | 34 | map make之后就可以添加元素了 35 | 36 | ```go 37 | m:=make(map[string]int) 38 | m["aa"]=1 39 | m["bb"]=2 40 | ``` 41 | 42 | 43 | ## 判断key是否存在 44 | 45 | ```go 46 | m:=make(map[string]int) 47 | m["aa"]=1 48 | m["bb"]=2 49 | 50 | v, found := m["aa"] //found为true 为1 51 | v, found := m["aaa"] //found为false 为0 52 | 53 | //通常和if表达是一起,判断key是否存在 54 | if vv,found:=m["bb"];found { //key存在 55 | //code 56 | } 57 | ``` 58 | 59 | 60 | ## 删除map中的元素 61 | 62 | map使用内置函数`delete`来删除元素。 63 | 64 | ```go 65 | delete(m,key) 66 | ``` 67 | 68 | ```go 69 | m:=make(map[string]int) 70 | m["aa"]=1 71 | 72 | delete(m,"aa") 73 | delete(m,"bb") // key不存在也不会报错 74 | ``` 75 | 76 | 77 | ## 遍历map 78 | 79 | golang 使用`for-range`遍历map 80 | 81 | ```go 82 | for k, v := range m { 83 | //code 84 | } 85 | ``` 86 | 87 | k,v分别代表key和这个key对于的值。 88 | 89 | 如果只想获取key可以 90 | 91 | ```go 92 | for k := range m { 93 | fmt.Println(key) 94 | } 95 | ``` 96 | 97 | 如果只想获取value则: 98 | 99 | ```go 100 | for _, v := range m { 101 | fmt.Println(v) 102 | } 103 | ``` 104 | 105 | ## map的一些特性 106 | 107 | - map中key是不排序的,所以你每一次对map进行`for-range`打印的结果可能不同 108 | - golang的map不是`线程安全`的,在并发读写的时候会触发`concurrent map read and map write`的panic。解决的办法是自己加把锁。 109 | - map在删除某个key的时候不是真正的删除,只是标记为空,空间还在,所以在for range删除map元素是安全的。 -------------------------------------------------------------------------------- /src/go语言基础/package和可见性.md: -------------------------------------------------------------------------------- 1 | # package和可见性 2 | 3 | ## go源码文件 4 | 5 | go项目的源码文件有如下三种 6 | 7 | - `.go`为文件名的go代码文件,在实际项目中这个是最常见的。 8 | - `.s`为结尾的go汇编代码文件,需要汇编加速的项目会用到,go内核源码比较多,实际项目中非常少见。 9 | - `.h .cpp .c`为结尾的c/c++代码,需要和c/c++交互的项目有看到,在实际项目也不较少见。 10 | 11 | 一般go编译器在编译时会扫描相关目录下的这些go源文件进行编译. 12 | 13 | 如下情况下go源码文件会忽略编译 14 | - `_test.go`为结尾的文件,这些文件是go的单元测试代码,用于测试go程序,不参与编译。 15 | - `_$GOOS_$GOARCH.go` `$GOOS`代码操作系统(windows,linux等),`$GOARCH`代表cpu架构平台(arm64,adm64等),go编译时会符合环境变量相关文件编译。 16 | - 编译过程中指定了条件编译参数`go build -tags tag.list`,编译器会选择性忽略一些文件不参与编译,参考[go build](../go开发环境搭建/go命令.md)。 17 | 18 | 19 | ## 包 20 | 21 | 包(package)是go语言模块化的实现方式,每个go代码需要在第一行非注释的代码注明package。 22 | 23 | go的package是基于目录的,同一个目录下的go代码只能用相同的package。 24 | 25 | ## main package 26 | 27 | go中`main package`是go可执行程序必须包含的一个package。该package下的`main`方法是go语言程序执行入口。 28 | 29 | 30 | ## go可见性规则 31 | 32 | golang中常量、变量、类型名称、函数名、结构字段名 以一个大写字母开头,这个对象就可以被外部包的代码所使用;标识符如果以小写字母开头,则对包外是不可见的,整个包的内部是可见。 33 | 34 | 35 | ```go 36 | 37 | //a目录下 38 | package a 39 | 40 | const COST="aa" //对外可见 41 | 42 | var A :=0 //对外可见 43 | var a :=0 //对外不可见 44 | 45 | func F1() { //对外可见 46 | 47 | } 48 | 49 | func f1(){ //对外不可见 50 | 51 | } 52 | 53 | type Student struct{ //对外可见 54 | Name string //对外可见 55 | age int //对外不可见 56 | } 57 | 58 | type student struct {//对外不可见 59 | 60 | } 61 | 62 | 63 | //main package 64 | 65 | package main 66 | 67 | import "a" 68 | 69 | func main(){ 70 | 71 | println(a.COST) //访问a package下的常量 72 | 73 | a.A ==1 //访问a package下 A变量 74 | a.a //编译报错 75 | a.F1() 76 | a.f1()//编译报错 77 | 78 | 79 | var stu a.Student //访问 a package下的Student结构体 80 | 81 | var stu1 a.student //student不可见 编译报错 82 | 83 | stu.Name = "nam1" //ok 84 | stu.age = 18 //不可见 编译报错 85 | } 86 | 87 | ``` -------------------------------------------------------------------------------- /src/go语言基础/panic和recover.md: -------------------------------------------------------------------------------- 1 | # panic和recover 2 | 3 | 有些错误编译期就能发现,编译的时候编译器就会报错,而有些运行时的错误编译器是没办法发现的。golang的运行时异常叫做`panic`。 4 | 5 | 在golang当切片访问越界,空指针引用等会引起`panic`,`panic`如果没有自己手动捕获的话,程序会中断运行并打印`panic`信息。 6 | 7 | ```go 8 | var a []int = []int{0, 1, 2} 9 | fmt.Println(a[3]) //panic: runtime error: index out of range [3] with length 3 10 | ``` 11 | 12 | ```go 13 | type A struct { 14 | Name string 15 | } 16 | 17 | var a *A 18 | fmt.Println(a.Name) //panic: runtime error: invalid memory address or nil pointer dereference 19 | ``` 20 | 21 | ## 手动panic 22 | 23 | 当程序运行中出现一些异常我们需要程序中断的时候我们可以手动`panic` 24 | 25 | ```go 26 | var model = os.Getenv("MODEL") //获取环境变量 MODEL 27 | if model == "" { 28 | panic("no value for $MODEL") //MODEL 环境变量未设置程序中断 29 | } 30 | ``` 31 | 32 | ## 使用recover捕获panic 33 | 34 | 我们在`defer`修饰的函数里面使用`recover`可以捕获的`panic`。 35 | 36 | ```go 37 | func main() { 38 | func() { 39 | defer func() { 40 | if err := recover(); err != nil { //recover 函数没有异常的是返回 nil 41 | fmt.Printf("panic: %v ", err) 42 | } 43 | }() 44 | func() { 45 | panic("panic") //函数执行出现异常 46 | }() 47 | }() 48 | 49 | fmt.Println("here") 50 | } 51 | ``` 52 | 执行结果是 53 | 54 | ``` 55 | anic: panic 56 | here 57 | ``` 58 | 59 | `panic`的产生后会终止当前函数运行,然后去检测当前函数的`defer`是否有`recover`,没有的话会一直往上层冒泡直至最顶层;如果中间某个函数的defer有`recover`则这个向上冒泡过程到这个函数就会终止。 60 | 61 | 62 | golang中`goroutine`没有父子关系,不能在一个`goroutine`中 recover另一个`goroutine`的 `panic`。 63 | 64 | ```go 65 | func main() { 66 | func() { 67 | defer func() { 68 | if err := recover(); err != nil { 69 | fmt.Printf("panic: %v ", err) 70 | } 71 | }() 72 | go func() { 73 | panic("panic") 74 | }() 75 | }() 76 | time.Sleep(1e9) 77 | fmt.Println("here") 78 | } 79 | ``` 80 | 执行结果是 81 | ``` 82 | panic: panic 83 | 84 | goroutine 6 [running]: 85 | main.main.func1.2() 86 | /workspace/gocode/test/cmd/test.go:16 +0x39 87 | created by main.main.func1 88 | /workspace/gocode/test/cmd/test.go:15 +0x57 89 | exit status 2 90 | ``` 91 | 我们看到虽然recover 捕获了错误信息,但是程序还是退出了。 -------------------------------------------------------------------------------- /src/go语言基础/string.md: -------------------------------------------------------------------------------- 1 | # 字符串 2 | 3 | ## 字符串的本质 4 | go内嵌`string`类型来表示字符串。我们来看`string`的本质 5 | 6 | ``` 7 | type StringHeader struct { 8 | Data uintptr 9 | Len int 10 | } 11 | ``` 12 | 13 | 本质是`string`是个结构体,有连个字段`Data`是个指针指向一段字节系列(连续内存)开始的位置。`Len`则代表长度,内嵌函数`len(s)`可以获取这个长度值。 14 | 15 | 16 | `Data`字段指向的是一段连续内存的起始文字,这一段内存的值是不允许改变的。 17 | 18 | ```go 19 | a:="aaa" // a{Data:&"aaa",Len:3} &"aaa"代表为“aaa”内存起始地址 20 | b:=a // b{Data:&"aaa",Len:3} 21 | c:=a[2:] // c{Data:&"aaa"+2,Len:3} 22 | ``` 23 | 24 | 25 | ## 字符串拼接 26 | 27 | go字符串拼接使用`+` 28 | 29 | ```go 30 | 31 | a:="hello" + " world" 32 | 33 | b := a+"ccc" 34 | 35 | ``` 36 | 37 | ## 字符串切片操作 38 | 39 | 字符串支持下标索引的方式索引到字符,`s[i]` i需要满足`0<=i 127) 10 | - int16(-32768 -> 32767) 11 | - int32(-2,147,483,648 -> 2,147,483,647) 12 | - int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807) 13 | 14 | 无符号整型 uint8,uint16,uint32,uint64分别对应8、16、32、64 bit大小的无符号整数。 15 | 16 | - uint8(0 -> 255) 17 | - uint16(0 -> 65,535) 18 | - uint32(0 -> 4,294,967,295) 19 | - uint64(0 -> 18,446,744,073,709,551,615) 20 | 21 | 22 | 在go语言中int是根据平台架构相关的类型,在32位平台int和int32相当,在64位平台int和int64相当。 23 | 24 | go语言中还有byte和uintptr类型也是整型, byte是unit8的别名用来表示一个字节,uintptr用来存放指针的类型,和int类型一样跟平台架构相关。 25 | 26 | ## 浮点型 27 | 28 | go语言中有两张浮点型float32和float64。float32精确到小数点后7 位,float64精确到小数点后15位。 29 | 30 | 二者的取值范围 31 | 32 | - float32(+- 1e-45 -> +- 3.4 * 1e38) 33 | - float64(+- 5 1e-324 -> 107 1e308) 34 | 35 | ## 布尔型 36 | 37 | 布尔型的值只能是 `true` 或者 `false`这两个go语言内置常量。 38 | 39 | 可以逻辑运算的类型进行逻辑运算会产生布尔型。 40 | 41 | ```go 42 | var a = 1 43 | a == 5 // false 44 | a == 1 // true 45 | 46 | aStr := "aa" 47 | aStr == "vvv" //false 48 | aStr == "aa" // true 49 | ``` 50 | 51 | 52 | ## 复数类型 53 | 54 | go内置`complex64`和`complex128`来表示复数,内嵌函数`real`,`imag`可以分别获得相应的实数和虚数部分。 55 | 56 | ## 类型转换 57 | 58 | 在golang中不支持隐式类型转换 59 | ```go 60 | var a int64 = 30 61 | var b int32 62 | b = a //编译器报错 63 | ``` 64 | 65 | 我们自己需要用类型转换表达式类转换类型 66 | 67 | ``` 68 | 类型名(表达式) 69 | ``` 70 | 71 | ```go 72 | var a int64 = 3 73 | var b int32 74 | b = int64(a) //ok 75 | 76 | var sum int = 90 77 | var count int = 50 78 | var mean float32 79 | mean = float32(sum)/float32(count) //ok 80 | ``` -------------------------------------------------------------------------------- /src/go语言基础/常量.md: -------------------------------------------------------------------------------- 1 | # 常量 2 | 3 | go使用`const`定义常量,常量是编译期能确定的结果,在go语言存储在常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串类型。 4 | 5 | ```go 6 | const a = 1 //a会被自动推断为int型 7 | const b = 1/2 8 | const c="abc" 9 | const length1 = len(c) //ok length=3 len常量结果为常量 10 | const length2 = len(c[:]) //not ok len变量结果为变量 11 | 12 | const c1, c2, c3 = "c", 2, "c2" //ok 13 | 14 | //使用带()的写法 15 | const ( 16 | Monday, Tuesday, Wednesday = 1, 2, 3 17 | Thursday, Friday, Saturday = 4, 5, 6 18 | ) 19 | 20 | //类似枚举的写法 21 | const( 22 | Monday=1 23 | Tuesday=2 24 | Wednesday=3 25 | ) 26 | 27 | 28 | ``` 29 | 30 | ## iota 简化常量写法 31 | 32 | ```go 33 | //使用iota 34 | const ( 35 | Monday = iota + 1 //1 36 | Tuesday //2 37 | Wednesday //3 38 | ) 39 | 40 | 41 | 42 | //go使用枚举的真正方式 43 | 44 | type MyType int //定义新的类型 45 | 46 | const ( 47 | Monday MyType = iota + 1 //1 48 | Tuesday //3 49 | Wednesday //3 50 | ) 51 | ``` 52 | -------------------------------------------------------------------------------- /src/go语言基础/指针类型.md: -------------------------------------------------------------------------------- 1 | # 指针类型 2 | 3 | 4 | go语言中也有指针,但是go语言中指针的不支持指针运算,例如 `a = *p++` 在go中是不支持的。 5 | 6 | 指针变量的本质是指向存储某个类型值的内存地址。所有的指针类型大小都一样受系统平台影响,跟指向的值类型无关。 7 | 8 | 指针使用 `变量名 *变量类型`申明。 指针类型的0值为`nil`。 9 | 10 | 11 | ``` 12 | var p *int //p 为指向int类型的指针,目前值为nil 13 | a := 1 14 | p = &a //p指针存了a变量的内存地址,类似0xc0000140e0这样的一个内存地址。 15 | 16 | *p = 2 //这里*的左右是让p指针指向的内存值改成2 17 | fmt.Println(*p,a) //2,2 a的值也变成2 18 | 19 | c:=3 20 | p= &c //p保持了c的内存地址,到吃p和a的关系解除 21 | ``` 22 | 23 | 24 | go的指针虽然不能进行运算,但是指针带来了操作内存的便捷性。同时在一些需要拷贝的场景(比如函数传参),使用指针可以减少额外的内存开销和提供性能。涉及指针的问题,后续章节会有更多的例子。 -------------------------------------------------------------------------------- /src/go语言基础/数组和切片.md: -------------------------------------------------------------------------------- 1 | # 数组和Slice(切片) 2 | 3 | ## 数组 4 | 5 | 数组是固定长度相同类型的集合。数组的长度必须是一个常量表达式(编译阶段就知道结果),数组元素的值可以由地址下标访问和修改的。 6 | 数组是比较特殊的类型,数组的长度是数组类型的一部分,像`[3]int`和`[6]int`二者是不同类型 7 | 8 | ```go 9 | var a [3]int 10 | var b []string 11 | 12 | 13 | var c = [5]int{1, 2, 15, 23, 16} 14 | var d = [...]int{9, 6, 7, 5, 12} // `...`可以替代写具体长度,编译器可以帮你算 15 | ``` 16 | 17 | ### 数组遍历 18 | 19 | 数组可以使用`for`和`for range`方式来遍历 20 | 21 | ```go 22 | var arr = [9]int{0, 1} 23 | for i := 0; i < len(arr); i++ { 24 | arr[i] = i * 2 25 | } 26 | 27 | for _, v := range arr { 28 | fmt.Println(v) 29 | } 30 | 31 | for i, _ := range arr { 32 | fmt.Println(arr[i]) 33 | } 34 | 35 | ``` 36 | 37 | 数组在golang中实践使用的比较少,很多场景中我们都使用`Slice`来替代。 38 | 39 | ## 切片(slice) 40 | 41 | 切片是对数组一段连续内存的引用,在golang中Slice的底层结构为 42 | 43 | ```go 44 | type SliceHeader struct { 45 | Data uintptr //指向该切片的索引的底层数组中的起始位置 46 | Len int //切片长度 47 | Cap int //切片总容量 48 | } 49 | ``` 50 | 51 | 内置函数`len`和`cap`可以分别获取切片的长度和大小。 52 | 53 | 54 | ### 切片的内存模型 55 | 56 | ```go 57 | var a=[5]int{1,2,3,4,5} 58 | b:=a[0:3] // b SliceHeader{&a[0],3,5} 59 | c:=a[3:5]// c SliceHeader{&a[3],2,2} 60 | ``` 61 | 62 | 63 | ### 切片表达式 64 | 65 | ```go 66 | var a=[5]int{1,2,3,4,5} 67 | b:=a[:3] //等同于 a[0:3] 68 | c:=a[3:] //等同于 a[3:5] 69 | d:=a[:] //等同于 a[0:5] 70 | ``` 71 | 72 | ### 使用make 创建 73 | 74 | ```go 75 | a := make([]int, 8, 9) //创建[]int且切片 len为8 cap为9 76 | aa := make([]int, 8) //参数cap可以省略 默认cap和len一样 77 | ``` 78 | 79 | 80 | ### 切片的复制 81 | 82 | golang内置函数`copy`来复制切片 83 | 84 | `copy`的声明格式`func copy(dist []T,src []T)int` ,copy的返回值为拷贝的长度 85 | 86 | ```go 87 | src1 := []int{1, 2, 3} 88 | src2 := []int{4, 5} 89 | dest := make([]int, 5) 90 | 91 | n := copy(dest, src1) //注意这边只拷贝了前三个元素 92 | n = copy(dest[3:], src2) //拷贝从第四个位置上 93 | ``` 94 | 95 | 96 | ### 切片追加 97 | 98 | golang内置函数`append`来追加切片。 99 | 100 | `append`声明格式`func append(dest[]T, x ...T) []T` 101 | 102 | `append`方法将多个具有相同类型的元素追加到`dest`切片后面并且返回新的切片,追加的元素必须和`dest`切片的元素同类型。 103 | 104 | 如果`dest`的切片容量不够,`append`会分配新的内存来保证已有切片元素和新增元素的存储。因此返回的切片所指向的底层数组可能和`dest`所指向的底层数组不相同。 105 | 106 | 107 | 108 | ```go 109 | a := []int{1, 2, 3} //a len:2 cap:3 110 | b = append(a, 4, 5, 6) //b: len:5 cap:5 b和a指向的数组已经不同 111 | 112 | c := append(a,b...) // 使用...快速展开b 113 | ``` 114 | 115 | 116 | ### 切片遍历 117 | 118 | 切片遍历和数组基本上没差别 119 | 120 | ```go 121 | var arr = make([]int,10) 122 | for i := 0; i < len(arr); i++ { 123 | arr[i] = i * 2 124 | } 125 | 126 | for _, v := range arr { 127 | fmt.Println(v) 128 | } 129 | 130 | for i, _ := range arr { 131 | fmt.Println(arr[i]) 132 | } 133 | ``` -------------------------------------------------------------------------------- /src/go语言基础/流程控制.md: -------------------------------------------------------------------------------- 1 | # 流程控制 2 | 3 | ## if-else 4 | 5 | golang中 if-else有如下几种结构 6 | 7 | ```go 8 | if 条件 { 9 | //代码 10 | } 11 | 12 | 13 | if 条件 { 14 | //代码 15 | } else { 16 | //代码 17 | } 18 | 19 | 20 | if 条件1 { 21 | //代码 22 | } else if 条件2 { 23 | //代码 24 | }else { 25 | //代码 26 | } 27 | ``` 28 | 29 | 30 | 31 | 32 | ## switch 33 | 34 | golang的`switch`条件控制比较强大,有如下几种方式 35 | 36 | ### `switch 变量` 方式 37 | 38 | 39 | ``` 40 | switch 变量 { 41 | case 值1: 42 | //代码 43 | case 值2: 44 | //代码 45 | default: 46 | //代码 47 | } 48 | ``` 49 | 50 | ```go 51 | var1 :=0 52 | switch var1 { 53 | case 1: 54 | fmt.Println(1) 55 | case 0: 56 | fmt.Println(0) 57 | default: 58 | fmt.Println("default") 59 | } 60 | ``` 61 | 62 | 63 | ``` 64 | switch 变量 { 65 | case 值1,值2: //case 后可以支持多个值做测试,多个值使用`,`分割 66 | //代码 67 | case 值3: 68 | //代码 69 | default: 70 | //代码 71 | } 72 | ``` 73 | 74 | 75 | ```go 76 | var1 :=99 77 | switch var1 { 78 | case 98,99,100: 79 | fmt.Println("more than 97") 80 | case 0: 81 | fmt.Println(0) 82 | default: 83 | fmt.Println("other") 84 | } 85 | ``` 86 | 87 | #### Go里面switch默认的情况下每个case最后都会`break`,匹配成功后不会自动向下执行其他case,而是跳出整个switch, 但是golang支持`fallthrough`强制执行下一个case的代码。 88 | 89 | ```go 90 | switch val { 91 | case 0: fallthrough 92 | case 1: 93 | func1() //i == 0 时函数也会被执行 94 | } 95 | ``` 96 | 97 | ### `switch` 不带变量方式,在case 分支使用条件判断语句 98 | 99 | ``` 100 | switch { 101 | case 条件1: 102 | //代码 103 | case 条件2: 104 | //代码 105 | default: 106 | //代码 107 | } 108 | ``` 109 | 110 | ```go 111 | i :=99 112 | switch { 113 | case i < 0: 114 | fmt.Println("le 0") 115 | case i == 0: 116 | fmt.Println("eq 0") 117 | case i > 0: 118 | fmt.Println("lg 0") 119 | } 120 | ``` 121 | 122 | 123 | 124 | 125 | ## for 126 | 127 | 在golang中for控制结构是最灵活的。在for循环体重`break`可以退出循环,`continue`可以忽略本次循环未执行的代码跳入下次循环。 128 | 129 | 130 | ### 正统的for结构 131 | 132 | ``` 133 | for 初始化语句; 条件语句; 修饰语句 { 134 | 135 | } 136 | ``` 137 | 138 | ```go 139 | for i := 0; i < 10; i++ { 140 | if i==3 { 141 | continue //不打印3 142 | } 143 | fmt.Println(i) 144 | } 145 | ``` 146 | 147 | ### 类似 `while` 结构 148 | 149 | ``` 150 | for 条件 { 151 | 152 | } 153 | ``` 154 | 155 | ```go 156 | i:=10 157 | for i >= 0 { 158 | i -= 1 159 | fmt.Println(i) 160 | } 161 | ``` 162 | 163 | ### 无限循环 164 | 165 | ``` 166 | for{ 167 | //代码 168 | } 169 | ``` 170 | 171 | 172 | ```go 173 | i :=10 174 | for{ 175 | i-=1 176 | if i == 0 { 177 | break //使用break退出无限循环 178 | } 179 | } 180 | ``` 181 | 182 | ### for-range 结构 183 | 184 | 在golang中 `for range`比仅仅可以遍历 数组,切片,map 还可以变量 带缓存的`channel` 185 | 186 | ```go 187 | 188 | arr :=[]int{1,3,3} 189 | 190 | for i, v := range myMap { 191 | fmt.Printf("index:%d value:%d \n", i, v) 192 | } 193 | 194 | 195 | myMap := map[string]int{ 196 | "a": 1, 197 | "b": 2, 198 | } 199 | for k, v := range myMap { 200 | fmt.Printf("key:%s value:%d \n", k, v) 201 | } 202 | 203 | 204 | channels := make(chan int, 10) 205 | go func() { 206 | for { 207 | channels <- 1 208 | time.Sleep(1e9) 209 | } 210 | }() 211 | for v := range channels { //当channels的缓存队列为空会阻塞 212 | fmt.Println(v) 213 | } 214 | ``` 215 | 216 | 217 | ### select 结构 218 | 219 | 在golang中还有一个特殊的控制结构叫`select`,`select`需要和channel配置使用,我们在channel的小节再详解介绍。 -------------------------------------------------------------------------------- /src/go语言基础/结构体和方法.md: -------------------------------------------------------------------------------- 1 | # 结构体和方法 2 | 3 | 结构体是数组组织的一种形式,类似面向对象语言中的`class`。golang中使用结构体和结构体方法有面向对象的一些类似特性。 4 | 5 | ## 结构体定义 6 | 7 | ```go 8 | type 结构体名称 struct { 9 | 字段名1 类型1 `字段标签` 10 | 字段名2 类型2 `字段标签` 11 | ... 12 | } 13 | ``` 14 | 15 | 结构体字段名的大小写决定了这个结构体在包外是否可见。 16 | 17 | ```go 18 | type Stu struct{ 19 | Name string //包外可见 20 | age int //包外不可见 21 | } 22 | ``` 23 | 24 | 结构体中的字段类型可以是任何类型,包括结构体本身,函数类型,接口类型。 25 | 26 | ```go 27 | 28 | type Inter interface{ 29 | FuncA(int) 30 | } 31 | 32 | type Ftype func(int) 33 | 34 | type S struct { 35 | fn Ftype //函数类型 36 | inter Inter //接口类型 37 | node *S //包含自己 38 | Name string 39 | } 40 | ``` 41 | 42 | 结构体的标签可以通过结构体`反射`获取,在结构体序列化和反序列化时长会见到。 43 | 比如结构体需要使用json序列化和反序列化是可能需要如下定义: 44 | ```go 45 | type Stu struct{ 46 | Name string `json:"name"` 47 | No string `json:"number"` 48 | age int //age对外不见不参与序列化 49 | } 50 | ``` 51 | 52 | ## 结构体初始化 53 | 54 | ```go 55 | type Stu struct{ 56 | Name string 57 | age int 58 | } 59 | 60 | var stuA = Stu{"wida",18} //不带字段名初始化,必须按照结构体定义的顺序赋值 61 | var stuB = Stu{Name:"amy",age:18} //带字段名初始化 62 | var stuC = Stu{Name:"jenny"} //带字段名初始化,不给age初始化, age是int初始化值为0 63 | ``` 64 | 65 | 一般在时间开发过程中,我们都建议使用带字段名初始化,这样防止在增加结构体字段的时候引起报错。 66 | 67 | 结构体还可以使用golang内置函数`new`初始化。使用`new`初始化返回的结构体指针。 68 | 69 | ```go 70 | var stuAPtr = new(Stu) 71 | stuAPtr.Name = "wida" 72 | stuAPtr.age = 18 73 | 74 | ``` 75 | 76 | 大多数情况下我们可以使用取地址符`&`来取代`new`函数。 77 | 78 | ```go 79 | var stuAPtr = &Stu{"wida",18} //这个等效也上面的new 初始化 80 | ``` 81 | 82 | ## 结构体嵌套 83 | 84 | golang中结构体是可以嵌套的,`A结构体`嵌套`Base结构体`可以隐式获取`Base结构体`的属性,实际上A结构体有个匿名字段为`Base`在名字冲突的时候可以使用这个字段解决冲突问题 85 | 86 | ```go 87 | 88 | type Base struct { 89 | Name string 90 | Age int 91 | } 92 | 93 | type A struct{ 94 | Base 95 | Gender int 96 | } 97 | 98 | type B struct { 99 | Base 100 | Name string 101 | } 102 | 103 | var a A 104 | 105 | a.Gender = 1 106 | a.Age = 18 //等同于a.Base.Age 107 | a.Name = "wida" //等同于a.Base.Name 108 | 109 | var b B 110 | b.Age = 18 111 | b.Name = "wida" //注意这个时候 b.Name是b自带的 而非b.Base.Name 112 | b.Base.Name = "amy" 113 | ``` 114 | 115 | ## 结构体方法 116 | 117 | 类似面向对象语言中的类方法,golang中结构体也有他的方法,在golang中结构体的方法有两中接收器(receiver),一种就是结构体结构体对象还一中是结构体结构体对象指针,需要注意二者在使用上的区别。 118 | 119 | ```go 120 | type Stu struct { 121 | Name string 122 | Age int 123 | } 124 | 125 | func (s Stu)GetName() string { 126 | return s.Name 127 | } 128 | 129 | func (s Stu)SetName(name string) { 130 | return s.Name = name 131 | } 132 | 133 | var a Stu 134 | 135 | a.SetName("aaa") 136 | 137 | fmt.Println(a) //{ 0} 这边SetName没有把a的Name赋值为aaa 138 | ``` 139 | 140 | `SetName`正确的写法应该是 141 | ```go 142 | 143 | func (s *Stu)SetName(name string) { 144 | return s.Name = name 145 | } 146 | 147 | a.SetName("aaa") //这边go会有一个隐式转换 a->*a 148 | fmt.Println(a) //{aaa 0} 149 | ``` 150 | 151 | 为什么结构体对象做接收器`SetName`方法不起作用? 在golang中参数都是拷贝传递的,事实上接收器其实是特色的一个参数,结构体对象接收器的方法会把接收器对象拷贝一份,对新对象赋值操作当然不能改变老的对象,而由于指针结构体接收器拷贝的是指针,实际上是指向同一个对象,所以修改就能生效。 在实际开发过程中我们一般都会使用结构体对象指针接收器,不仅仅可以规避赋值不生效的问题,而且还可以防止大的对象发生拷贝的过程。 152 | 153 | 有个情况需要特别注意,在golang中`nil`是可以作为接收器的。 154 | 155 | ```go 156 | func (s *Stu) SetName(name string) { 157 | if s == nil { 158 | return 159 | } 160 | s.Name = name 161 | } 162 | 163 | var aPtr *Stu //aPtr是指针 初值是nil 164 | aPtr.SetName("ddd") //代码没有效果,但是不会报错 165 | ``` 166 | 167 | 168 | ## 方法继承 169 | 170 | 类似结构体字段的继承,结构体`A`内嵌另一个结构体`B`时,属于`B`的方法也会别`A`继承。 171 | 172 | ```go 173 | type Base struct {} 174 | 175 | func (b *Base)Say() { 176 | fmt.Println("hi") 177 | } 178 | 179 | type A struct { 180 | Base 181 | } 182 | 183 | var a A 184 | a.Say() 185 | ``` -------------------------------------------------------------------------------- /src/go语言基础/运算.md: -------------------------------------------------------------------------------- 1 | # 运算 2 | 3 | ## 算数运算 4 | 5 | 整型和浮点型支持 +、-、* 和 /。 6 | 7 | 整型还支持%(取余数)。 8 | 9 | 10 | ## 逻辑运算 11 | 12 | go 整型,浮点型,string 支持 ==、!=、<、<=、>、>=逻辑运算,逻辑运算的结果会产生bool类型。 13 | 14 | ```go 15 | //string 逻辑运算 使用的是按字节逐一对比的 16 | "ab" >"c" //false 17 | ``` 18 | 19 | ## 位运算 20 | 21 | ### 二元运算符: 22 | 23 | 按位与 &: 24 | 25 | ```go 26 | 1 & 1 // 1 27 | 1 & 0 // 0 28 | 0 & 0 // 0 29 | ``` 30 | 31 | 按位或 |: 32 | 33 | ```go 34 | 1 | 1 // 1 35 | 1 | 0 // 1 36 | 0 | 0 // 0 37 | ``` 38 | 39 | 40 | 按位异或 ^: 41 | 42 | ```go 43 | 1 ^ 1 // 0 44 | 1 ^ 0 // 1 45 | 0 ^ 0 // 0 46 | ``` 47 | 48 | 位清除 &^: 49 | 50 | `a &^ b`表示把a中,ab都为1的位置为0 51 | 52 | ```go 53 | 0110 &^ 1011 // 0100 54 | 1011 &^ 1101 // 0010 55 | ``` 56 | 57 | ### 一元运算符 58 | 59 | 按位取反^: 60 | 61 | 对应位置 0变成1,1变成0。对于正的有符号数,取反会变负数,所以负的有符号数取反都等于1. 62 | 63 | ```go 64 | ^1 // -2 65 | ^-3 // 2 66 | ``` 67 | 68 | 69 | 位左移 <<: 70 | 左移时右侧位补0,移动一位相当于乘以2 71 | ```go 72 | 1 << 10 // 等于 1k 73 | 1 << 20 // 等于 1m 74 | 1 << 30 // 等于 1g 75 | ``` 76 | 77 | 位右移 >>: 78 | 右移时左侧位补0,移动一位相对于乘以2 79 | 80 | ```go 81 | 39 >>2 // 9 82 | 512 >> 2 //128 83 | ``` 84 | 85 | 86 | ## 运算符与优先级 87 | 88 | 由上至下代表优先级由高到低 89 | 90 | ``` 91 | 优先级 运算符 92 | 8 ()(括号) 93 | 7 ^ ! 94 | 6 * / % << >> & &^ 95 | 5 + - | ^ 96 | 4 == != < <= >= > 97 | 3 <- 98 | 2 && 99 | 1 || 100 | ``` 101 | 102 | -------------------------------------------------------------------------------- /src/go语言基础/错误处理.md: -------------------------------------------------------------------------------- 1 | # 错误处理 2 | 3 | 在golang中错误有专门的一个包`errors`来处理,golang定义了error的接口,还允许自定义错误类型。 4 | 5 | ```go 6 | type error interface { 7 | Error() string 8 | } 9 | ``` 10 | 错误支持`==`和`!=`操作。 11 | 12 | ## 定义简单错误 13 | 14 | `errors`包中提供`New`方法来生成`error`(底层类型是`error.errorString`)。 15 | 16 | ```go 17 | err1 := errors.New("error1") 18 | err2 := errors.New("error2") 19 | 20 | func makeErr(a int) error { 21 | if a==1 { 22 | return err1 23 | } 24 | if a==2 { 25 | return err2 26 | } 27 | return nil 28 | } 29 | 30 | fmt.Println(err1 == makeErr(1)) 31 | fmt.Println(err1 == makeErr(2)) 32 | ``` 33 | 34 | ## 自定义错误 35 | 36 | 只有实现`error`接口,你就可以定义新的一种错误类型 37 | 38 | ```go 39 | type myError struct { 40 | s string 41 | code int 42 | } 43 | 44 | func (m *myError) Error() string { 45 | return fmt.Sprintf("code:%d,msg:%s", m.code, m.s) 46 | } 47 | 48 | var err1 = &myError{"error1", 100} 49 | var err2 = &myError{"error1", 100} 50 | 51 | func makeErr(a int) error { 52 | if a == 1 { 53 | return err1 54 | } 55 | if a == 2 { 56 | return err2 57 | } 58 | return nil 59 | } 60 | fmt.Println(makeErr(1)) //code:100,msg:error1 61 | fmt.Println(err1 == makeErr(1)) //true 62 | fmt.Println(err1 == makeErr(2)) //true 63 | ``` 64 | 65 | ## 错误处理技巧 66 | 67 | 在`go1.13`之前,go的错误处理方式代码写起来相当繁琐。`go1.13`吸收了go社区一些优秀的错误处理方式([pkg/errors](https://github.com/pkg/errors)),彻底解决被人诟病的问题。本文主要介绍的错误处理方式是基于`go1.13`的。 68 | 69 | `go 1.13`的`error`包 增加的`errors.Unwrap`,`errors.As`,`errors.Is`三个方法。 70 | 同时 `fmt` 包增加 `fmt.Errorf("%w", err)`的方式来wrap一个错误。 71 | 72 | 我们通过代码来了解它们的用法。 73 | ```golang 74 | package main 75 | 76 | import ( 77 | "fmt" 78 | "errors" 79 | ) 80 | type Err struct { 81 | Code int 82 | Msg string 83 | } 84 | func (e *Err) Error() string { 85 | return fmt.Sprintf("code : %d ,msg:%s",e.Code,e.Msg) 86 | } 87 | var A_ERR = &Err{-1,"error"} 88 | func a() error { 89 | return A_ERR 90 | } 91 | 92 | func b() error { 93 | err := a() 94 | return fmt.Errorf("access denied: %w", err) //使用fmt.Errorf wrap 另一个错误 95 | } 96 | 97 | func main() { 98 | err := b() 99 | er := errors.Unwrap(err) //如果一个错误包含 Unwrap 方法则返回这个错误,如果没有则返回nil 100 | fmt.Println(er ==A_ERR ) 101 | 102 | 103 | fmt.Println(errors.Is(err,A_ERR)) // 递归调用Unwrap判断是否包含 A_ERR 104 | var e = &Err{} 105 | fmt.Println( errors.As(err, &e)) 106 | 107 | if errors.As(err, &e) { // 递归调用Unwrap是否包含A_ERR,如果有这赋值给e 108 | fmt.Printf("code : %d ,msg:%s",e.Code,e.Msg) 109 | } 110 | } 111 | ``` 112 | 113 | 运行代码 114 | ```bash 115 | $ go run main.go 116 | true 117 | true 118 | true 119 | code : -1 ,msg:error 120 | ``` 121 | 122 | 错误为什么为要被wrap? 123 | 124 | 在一个函数A中错误发生的时候,我们会返回这个错误,函数B调用函数A拿到这个错,但是函数B不想做其他处理,它也返回错误,但是要打上自己的信息,说明这个错误经过了B函数,所以Wrap err就有了使用场景。 125 | 126 | 用了wrap后,错误是链状结构,我们用`errors.Unwrap`,逐级遍历err。还有我们有时候不一定会关心所有链条上的错误类型,我们只判断是否包含某种特点错误类型,所以 `errors.Is`和`errors.As` 方法就出现了。 127 | 128 | ## 带上函数调用栈信息 129 | 130 | 标准库的错误处理基本上能我们日常的开发需求,而且基本上能做到很优雅的错误处理。但是有时候我们还想带上更多信息,比如函数调用栈。我们使用第三方库[pingcap errors](https://github.com/pingcap/errors)来实现。 131 | 132 | ```golang 133 | package main 134 | import ( 135 | "fmt" 136 | pkgerr "github.com/pingcap/errors" 137 | ) 138 | type Err struct { 139 | Code int 140 | Msg string 141 | } 142 | func (e *Err) Error() string { 143 | return fmt.Sprintf("code : %d ,msg:%s",e.Code,e.Msg) 144 | } 145 | var A_ERR = &Err{-1,"error"} 146 | 147 | func stackfn1() error { 148 | return pkgerr.WithStack(A_ERR) 149 | } 150 | 151 | func main() { 152 | err := stackfn1() 153 | fmt.Printf("%+v",err) //这边使用 “%+v” 154 | } 155 | ```要介绍的错误处理方式是基于go1.13的。 156 | $ go run main.go 157 | code : -1 ,msg:error 158 | main.stackfn1 159 | /home/wida/gocode/goerrors-demo/main.go:18 160 | main.main 161 | /home/wida/gocode/goerrors-demo/main.go:22 162 | runtime.main 163 | /home/wida/go/src/runtime/proc.go:203 164 | runtime.goexit 165 | /home/wida/go/src/runtime/asm_amd64.s:1357 166 | Process finished with exit code 0 167 | ``` 168 | 有了函数调用栈信息,我们可以更好的定位错误。 -------------------------------------------------------------------------------- /src/go调试/GODEBUG追踪gc.md: -------------------------------------------------------------------------------- 1 | # GODEBUG追踪GC 2 | 3 | 上一小节我们介绍了通过设置环境变量`GODEBUG`追踪go调度器的信息的方法。其实`GODEBUG`还可以追踪go`GC`,本小节将介绍如何用`GODEBUG`追踪go的`GC`。 4 | 5 | 我们先写一个demo程序 6 | 7 | ```go 8 | package main 9 | import ( 10 | "runtime/debug" 11 | "time" 12 | ) 13 | func main() { 14 | num := 500000 15 | var bigmap = make(map[int]*[512]float32) 16 | for i := 0;i < num;i++ { 17 | bigmap[i] = &[512]float32{float32(i)} 18 | } 19 | 20 | println(len(bigmap)) 21 | time.Sleep(15e9) 22 | for i := 0;i < num;i++ { 23 | delete(bigmap,i) 24 | } 25 | 26 | debug.FreeOSMemory() 27 | time.Sleep(1000e9) 28 | } 29 | ``` 30 | 我先编译下 31 | ```bash 32 | $ go build -o test 33 | ``` 34 | 35 | ## 追踪go GC 36 | 37 | 运行`GODEBUG=gctrace=1` 38 | 39 | ```bash 40 | $ GODEBUG=gctrace=1 ./test 41 | ``` 42 | 43 | 会显示如下信息 44 | ``` 45 | ... 46 | 500000 47 | gc 10 @16.335s 0%: 0.004+1.0+0.004 ms clock, 0.016+0/1.0/2.9+0.016 ms cpu, 1006->1006->0 MB, 1784 MB goal, 4 P (forced) 48 | scvg: 1 MB released 49 | scvg: inuse: 835, idle: 188, sys: 1023, released: 17, consumed: 1005 (MB) 50 | forced scvg: 1005 MB released 51 | forced scvg: inuse: 0, idle: 1023, sys: 1023, released: 1023, consumed: 0 (MB) 52 | ``` 53 | 54 | 我们可以在[go runtime](https://golang.org/pkg/runtime/) 看到gc日志的格式的相关信息 55 | 56 | ### gc日志 57 | 58 | 格式如下: 59 | ` gc # @#s #%: #+#+# ms clock, #+#/#/#+# ms cpu, #->#-># MB, # MB goal, # P` 60 | 61 | ``` 62 | gc 10 @16.335s 0%: 0.004+1.0+0.004 ms clock, 0.016+0/1.0/2.9+0.016 ms cpu, 1006->1006->0 MB, 1784 MB goal, 4 P (forced) 63 | ``` 64 | - gc 10: gc的流水号,从1开始自增 65 | - @16.335s: 从程序开始到当前打印是的耗时 66 | - 0%: 程序开始到当前CPU时间百分比 67 | - 0.004+1.0+0.004 ms clock: 0.004表示STW时间;1.0表示并发标记用的时间;0.004表示markTermination阶段的STW时间 68 | - 0.016+0/1.0/2.9+0.016 ms cpu: 0.016表示整个进程在mark阶段STW停顿时间;0/1.0/2.9有三块信息,0是mutator assists占用的时间,2.9是dedicated mark workers+fractional mark worker占用的时间,2.9+是idle mark workers占用的时间。0.016 ms表示整个进程在markTermination阶段STW停顿时间(0.050 * 8)。 69 | - 1006->1006->0 MB: GC开始、GC结束、存活的heap大小 70 | - 1784 MB goal:下一次触发GC的内存占用阈值 71 | - 4 P: 处理器数量 72 | - (forced): 可能没有,代表程序中runtime.GC() 被调用 73 | 74 | ### scvg日志: 75 | 76 | go语言把内存归还给操作系统的过程叫scavenging,scvg日志记录的就是这个过程的日志信息。 77 | scvg 每次会打印两条日志 78 | 格式: 79 | ``` 80 | scvg#: # MB released printed only if non-zero 81 | scvg#: inuse: # idle: # sys: # released: # consumed: # (MB) 82 | ``` 83 | ``` 84 | scvg: 1 MB released 85 | scvg: inuse: 835, idle: 188, sys: 1023, released: 17, consumed: 1005 (MB) 86 | ``` 87 | - 1 MB released: 返回给系统1m 内存 88 | - inuse: 正在使用的 89 | - idle :空闲的 90 | - sys : 系统映射内存 91 | - released:已经归还给系统的内存 92 | - consumed:已经向操作系统申请的内存 93 | 94 | 95 | 我们看到`debug.FreeOSMemory()`运行后 96 | ``` 97 | forced scvg: 1005 MB released 98 | forced scvg: inuse: 0, idle: 1023, sys: 1023, released: 1023, consumed: 0 (MB) 99 | ``` 100 | 这个时候`released`内存是1023m,说明go语言已经正常把内存交给操作系统了了。可是RSS信息显示go仍然占用这这个内存。 101 | 102 | ![1](../img/1.png) 103 | 104 | 这又是为什么?golang在把内存规划给类linux系统(linux-like)时发送的指令是`建议回收`指令,那么系统可以根据自己负载情况决定是否真的回收。 105 | 106 | 在[go runtime](https://golang.org/pkg/runtime/)中我们找到一段如下的文字。 107 | ``` 108 | madvdontneed: setting madvdontneed=1 will use MADV_DONTNEED 109 | instead of MADV_FREE on Linux when returning memory to the 110 | kernel. This is less efficient, but causes RSS numbers to drop 111 | more quickly. 112 | ``` 113 | 翻译一下: 114 | 115 | ``` 116 | madvdontneed:如果设置GODEBUG=madvdontneed=1,golang归还内存给操作系统的方式将使用MADV_DONTNEED,而不是Linux上的MADV_FREE的方式。虽然MADV_DONTNEED效率较低,但会程序RSS下降得更快速。 117 | ``` 118 | 119 | 动手试一试 120 | ```bash 121 | $ GODEBUG=madvdontneed=1 go run main.go 122 | ``` 123 | ![2](../img/2.png) 124 | 这下RSS正常了。 125 | 126 | 127 | ### MADV_FREE和MADV_DONTNEED 128 | 129 | MADV 是linux kernel的内存回收的方式,有两种变体(MADV_FREE和MADV_DONTNEED,可以参考 130 | [MADV_FREE functionality](http://lkml.iu.edu/hypermail/linux/kernel/0704.3/3962.html)文档。 131 | 132 | 133 | ## 总结 134 | 135 | 本小节介绍了用`GODEBUG`追踪go GC的方法,介绍了tracing日志的格式。GC的追踪在排查程序内存占用问题特别有用。 -------------------------------------------------------------------------------- /src/go项目管理/go-modules.md: -------------------------------------------------------------------------------- 1 | # go modules 2 | 3 | ## go modules 介绍 4 | 5 | go modules是go 1.11中的一个实验性选择加入功能。目前随着go 1.14 的发布,越来越多的项目已经采用go modules方式作为项目的包依赖管理。 6 | 7 | ### 设置 GO111MODULE 8 | 9 | GO111MODULE 有三个值 off, on和auto,go.1.13以上的版本默认都是开启go modules的 。 10 | 11 | - off:go tool chain 不会支持go module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或者GOPATH/src模式来查找。 12 | - on:go tool chain 会使用go modules,而不会去GOPATH/src目录下查找,依赖包文件保持在$GOPATH/pkg下,允许同一个package多个版本并存,且多个项目可以共享缓存的module。 13 | - auto:go tool chain将会根据当前目录来决定是否启用module功能。当前目录在$GOPATH/src之外且该目录包含go.mod文件或者当前文件在包含go.mod文件的目录下面则会开启 go modules。 14 | 15 | 16 | ### go mod 命令介绍 17 | 18 | go modules 在golang中使用go mod命令来实现。 19 | 20 | ``` 21 | $ go help mod 22 | 23 | download download modules to local cache (下载依赖包到本地) 24 | edit edit go.mod from tools or scripts (编辑go.mod) 25 | graph print module requirement graph (列出模块依赖图) 26 | init initialize new module in current directory (在当前目录下初始化go module) 27 | tidy add missing and remove unused modules (下载缺失模块和移除不需要的模块) 28 | vendor make vendored copy of dependencies (将依赖模块拷贝到vendor下) 29 | verify verify dependencies have expected content (验证依赖模块) 30 | why explain why packages or modules are needed (解释为什么需要依赖) 31 | ``` 32 | 33 | ## go mod 使用 34 | 35 | 创建目录 36 | ``` 37 | $ mkdir -p ~/gocode/hello 38 | $ cd ~/gocode/hello/ 39 | ```` 40 | 41 | 初始化新module 42 | ``` 43 | $ go mod init github.com/youname/hello 44 | 45 | go: creating new go.mod: module github.com/youname/hello 46 | ``` 47 | 写代码 48 | 49 | ``` 50 | $ cat < hello.go 51 | package main 52 | 53 | import ( 54 | "fmt" 55 | "rsc.io/quote" 56 | ) 57 | 58 | func main() { 59 | fmt.Println(quote.Hello()) 60 | } 61 | EOF 62 | ```` 63 | 构建项目 64 | 65 | ``` 66 | $ go build 67 | $ ./hello 68 | 你好,世界。 69 | ``` 70 | 71 | 这个时候看下 go.mod 文件 72 | 73 | ``` 74 | $ cat go.mod 75 | 76 | module github.com/you/hello 77 | 78 | require rsc.io/quote v1.5.2 79 | ``` 80 | 81 | 一旦项目使用go modules的方式解决包依赖,你日常的工作就是在你的go项目代码中添加improt语句,标准命令(go build 或者go test)将根据需要自动添加新的依赖包(更新go.mod并下载新的依赖)。 82 | 在需要时可以使用go get foo@v1.2.3,go get foo@master,go get foo@e3702bed2或直接编辑go.mod等命令选择更具体的依赖项版本。 83 | 84 | 85 | 以下还有些常用功能: 86 | ``` 87 | go list -m all - 查看将在构建中用于所有直接和间接依赖模块的版本信息 88 | go list -u -m all - 查看所有直接和间接依赖模块的可用版本升级信息 89 | go get -u -会升级到最新的次要版本或者修订版本(x.y.z, z是修订版本号, y是次要版本号) 90 | go get -u=patch - 升级到最新的修订版本 91 | go build ./... 或者go test ./... - 从模块根目录运行时构建或测试模块中的所有依赖模块 92 | ``` 93 | 94 | 95 | ### go.mod 文件介绍 96 | 97 | go.mod 有module, require、replace和exclude 四个指令 98 | module 指令声明其标识,该指令提供模块路径。模块中所有软件包的导入路径共享模块路径作为公共前缀。模块路径和从go.mod程序包目录的相对路径共同决定了程序包的导入路径。 99 | require 指令指定的依赖模块 100 | replace 指令可以替换依赖模块 101 | exclude 指令可以忽略依赖项模块 102 | 103 | 104 | ### 国内goher使用go modules 105 | 106 | 由于墙的原因,国内开发者在使用go modules的时候会遇到很多依赖包下载不了的问题,下面提供几个解决方案 107 | 108 | - 使用go proxy(推荐) 109 | ``` 110 | export GOPROXY="https://goproxy.io" 111 | ``` 112 | - 使用go mod replace 替换下载不了的包,例如golang.org下面的包 113 | 114 | ``` 115 | replace ( 116 | golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a 117 | ) 118 | ``` 119 | 120 | 121 | 122 | ## 补充资料 123 | 124 | ### 模块版本定义规则 125 | 126 | 模块必须根据[semver](https://semver.org/)进行语义版本化,通常采用v(major).(minor).(patch)的形式,例如v0.1.0,v1.2.3或v1.5.0-rc.1。版本必需是v字母开头。 go mod 在拉取对应包版本的时候会找相应包的git tag(release tag和 pre release tag),如果对应包没有git tag 就会来取master 而对应的版本好会变成v0.0.0。go mod工具中的版本号格式为版本号 + 时间戳 + hash以下的版本都是合法的: 127 | 128 | ``` 129 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 130 | github.com/PuerkitoBio/goquery v1.4.1 131 | gopkg.in/yaml.v2 <=v2.2.1 132 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 133 | latest 134 | ``` 135 | 136 | 137 | # 总结 138 | 139 | 本小节介绍golang modules的使用,go modules本身有很多比较复杂的设计,你可以通过go modules[官方英文文档](https://github.com/golang/go/wiki/Modules#quick-start)做详细了解。go modules对golang 项目构建的基石,在实际项目中一定会经常接触到。 140 | -------------------------------------------------------------------------------- /src/go项目管理/go-test.md: -------------------------------------------------------------------------------- 1 | # go test 2 | 3 | go test是golang的轻量化单元测试工具,结合testing这个官方包,可以很方便为golang程序写单元测试和基准测试。 4 | 5 | 在之前go build命令介绍的时候说过,go build 编译包时,会忽略以“_test.go”结尾的文件。这些被忽略的_test.go文件正是go test的一部分。 6 | 7 | 在以_test.go为结尾的文件中,常见有两种类型的函数: 8 | 9 | - 测试函数,以Test为函数名前缀的函数,用于测试程序是否按照正确的方式运行;使用go test命令执行测试函数并报告测试结果是PASS或FAIL。 10 | - 基准测试(benchmark)函数,以Benchmark为函数名前缀的函数,它们用于衡量一些函数的性能;基准测试函数中一般会多次调用被测试的函数,然后收集平均执行时间。 11 | 12 | 13 | 无论是测试函数或者是基准测试函数都必须`import testing` 14 | 15 | ## 测试函数 16 | 17 | 测试函数的签名 18 | 19 | ```go 20 | func TestA(t *testing.T){ 21 | 22 | } 23 | ``` 24 | 例如我们看下go的官方包 bytes.Compare 函数的测试 25 | 26 | 27 | ```go 28 | package test 29 | 30 | import ( 31 | "bytes" 32 | "testing" 33 | ) 34 | 35 | var Compare = bytes.Compare 36 | 37 | func TestCompareA(t *testing.T) { 38 | var b = []byte("Hello Gophers!") 39 | if Compare(b, b) != 0 { 40 | t.Error("b != b") 41 | } 42 | if Compare(b, b[:1]) != 1 { 43 | t.Error("b > b[:1] failed") 44 | } 45 | } 46 | ``` 47 | 48 | 我们执行下 ```go test```,这个命令会遍历当前目录下所有的测试函数. 49 | ``` 50 | $ go test 51 | PASS 52 | ok github.com/wida/gocode/test 0.001s 53 | ``` 54 | 55 | 参数-v用于打印每个测试函数的名字和运行时间: 56 | 57 | ``` 58 | $ go test -v 59 | === RUN TestCompareA 60 | --- PASS: TestCompareA (0.00s) 61 | === RUN TestCompareB 62 | --- PASS: TestCompareB (0.00s) 63 | PASS 64 | ok github.com/wida/gocode/test 0.002s 65 | ``` 66 | 67 | 参数-run 用于运行制定的测试函数, 68 | 69 | ``` 70 | $ go run -v -run "TestCompareA" 71 | === RUN TestCompareA 72 | --- PASS: TestCompareA (0.00s) 73 | PASS 74 | ok github.com/wida/gocode/test 0.002s 75 | ``` 76 | run 后面的参数是正则匹配 -run "TestCompare" 会同时执行 TestCompareA 和 TestCompareB。 77 | 78 | 79 | ### 表驱动测试 80 | 81 | 在实际编写测试代码时,通常把要测试的输入值和期望的结果写在一起组成一个数据表(table),表(table)中的每条记录代表是一个含有输入值和期望值。还是看官方bytes.Compare的测试例子: 82 | 83 | ```go 84 | var compareTests = []struct { 85 | a, b []byte 86 | i int 87 | }{ 88 | {[]byte(""), []byte(""), 0}, 89 | {[]byte("a"), []byte(""), 1}, 90 | {[]byte(""), []byte("a"), -1}, 91 | {[]byte("abc"), []byte("abc"), 0}, 92 | {[]byte("abd"), []byte("abc"), 1}, 93 | {[]byte("abc"), []byte("abd"), -1}, 94 | {[]byte("ab"), []byte("abc"), -1}, 95 | {[]byte("abc"), []byte("ab"), 1}, 96 | {[]byte("x"), []byte("ab"), 1}, 97 | {[]byte("ab"), []byte("x"), -1}, 98 | {[]byte("x"), []byte("a"), 1}, 99 | {[]byte("b"), []byte("x"), -1}, 100 | // test runtime·memeq's chunked implementation 101 | {[]byte("abcdefgh"), []byte("abcdefgh"), 0}, 102 | {[]byte("abcdefghi"), []byte("abcdefghi"), 0}, 103 | {[]byte("abcdefghi"), []byte("abcdefghj"), -1}, 104 | {[]byte("abcdefghj"), []byte("abcdefghi"), 1}, 105 | // nil tests 106 | {nil, nil, 0}, 107 | {[]byte(""), nil, 0}, 108 | {nil, []byte(""), 0}, 109 | {[]byte("a"), nil, 1}, 110 | {nil, []byte("a"), -1}, 111 | } 112 | 113 | func TestCompareB(t *testing.T) { 114 | for _, tt := range compareTests { 115 | numShifts := 16 116 | buffer := make([]byte, len(tt.b)+numShifts) 117 | for offset := 0; offset <= numShifts; offset++ { 118 | shiftedB := buffer[offset : len(tt.b)+offset] 119 | copy(shiftedB, tt.b) 120 | cmp := Compare(tt.a, shiftedB) 121 | if cmp != tt.i { 122 | t.Errorf(`Compare(%q, %q), offset %d = %v; want %v`, tt.a, tt.b, offset, cmp, tt.i) 123 | } 124 | } 125 | } 126 | } 127 | ``` 128 | ``` 129 | $ go test -v -run "TestCompareB" 130 | === RUN TestCompareB 131 | --- PASS: TestCompareB (0.00s) 132 | PASS 133 | ok github.com/wida/gocode/test 0.004s 134 | ``` 135 | 136 | ## 基准测试函数 137 | 138 | 基准测试函数的函数签名如下 139 | 140 | ```go 141 | func BenchmarkTestB(b *testing.B) { 142 | 143 | } 144 | ``` 145 | 146 | 我们还是一官方的bytes.Compare 为例写一个基准测试 147 | 148 | ```go 149 | func BenchmarkComare(b *testing.B) { 150 | 151 | for i := 0; i < b.N; i++ { 152 | Compare([]byte("abcdefgh"), []byte("abcdefgh")) 153 | } 154 | } 155 | ``` 156 | 157 | 基准测试的运行 需要加 -bench参数 158 | 159 | ``` 160 | $ go test -bench BenchmarkCompare 161 | goos: linux 162 | goarch: amd64 163 | pkg: github.com/wida/gocode/test 164 | BenchmarkCompare-4 30000000 35.4 ns/op 165 | PASS 166 | ok github.com/wida/gocode/test 1.111s 167 | ``` 168 | 报告显示我们的测试程序跑了30000000次,每次平均耗时35.4纳秒。 169 | 170 | ### 使用 b.ResetTimer 171 | 172 | 有些时候我们的基础测试函数逻辑有点复杂或者在准备测试数据,如下 173 | 174 | ```go 175 | func BenchmarkComare(b *testing.B) { 176 | //准备数据 177 | ... 178 | b.ResetTimer() 179 | for i := 0; i < b.N; i++ { 180 | Compare([]byte("abcdefgh"), []byte("abcdefgh")) 181 | } 182 | } 183 | ``` 184 | 我们可以使用 b.ResetTimer() 将准备数据的时间排除在总统计时间之外。 185 | -------------------------------------------------------------------------------- /src/go项目管理/go命令.md: -------------------------------------------------------------------------------- 1 | # go 命令 2 | 3 | go语言本身自带一个命令行工具,这些命令在项目开发中会反复的使用,我们发点时间先了解下。我们看下完整的go 命令: 4 | 5 | ``` 6 | $ go 7 | Go is a tool for managing Go source code. 8 | 9 | Usage: 10 | go [arguments] 11 | The commands are: 12 | bug start a bug report 13 | build compile packages and dependencies 14 | clean remove object files and cached files 15 | doc show documentation for package or symbol 16 | env print Go environment information 17 | fix update packages to use new APIs 18 | fmt gofmt (reformat) package sources 19 | generate generate Go files by processing source 20 | get download and install packages and dependencies 21 | install compile and install packages and dependencies 22 | list list packages or modules 23 | mod module maintenance 24 | run compile and run Go program 25 | test test packages 26 | tool run specified go tool 27 | version print Go version 28 | vet report likely mistakes in packages 29 | ``` 30 | 31 | 32 | 在这些go命令中,有些命令相对功能简单,有些则相对复杂些,我们分几个小节介绍这些命令的使用场景。本小节我们先看 go build 和 go get。 33 | 34 | ## go build 35 | 36 | 37 | go build命令 编译我们指定的go源码文件,或者指定路径名的包,以及它们的依赖包。 38 | 如果我们在执行go build命令时没有任何参数,那么该命令将试图编译当前目录所对应的代码文件。 39 | 40 | 在编译main 包时,将以第一个参数文件或者路径,作为输出的可执行文件名,例如 41 | 第一个源文件('go build ed.go rx.go' 生产'ed'或'ed.exe') 42 | 或源代码目录('go build unix/sam'生成'sam'或'sam.exe')。 43 | 44 | 编译多个包或单个非main包时,go build 编译包但丢弃生成的对象,这种情况仅用作检查包是否可以顺利编译通过。 45 | 46 | go build 编译包时,会忽略以“_test.go”结尾的文件。 47 | 48 | 我们平常在开发一些兼容操作系统底层api的项目时,我们可以根据相应的操作系统编写不同的兼容代码,例如我们在处理signal的项目,linux和Windows signal是有差异的,我们可以通过加操作系统后缀的方式来命名文件,例如sigal_linux.go sigal_windows.go,在go build的时候会更加当前的操作系统($GOOS 环境变量)来下选择编译文件,而忽略非该操作系统的文件。上面的例子如果在linux下 go build 会编译 sigal_linux.go 而忽略 sigal_windows.go,而windows系统下在反之。 49 | 50 | go build 有好多的个参数,其中比较常用的是 51 | 52 | - -o 指定输出的文件名,可以带上路径,例如 go build -o a/b/c。 53 | - -race 开启编译的时候检测数据竞争。 54 | - -gcflags 这个参数在编译优化和逃逸分析中经常会使用到。 55 | 56 | ### go build -tags 57 | 58 | 上面我们介绍了go build 可以使用加操作系统后缀的文件名来选择性编译文件。还有一种方法来实现条件编译,那就是使用`go build -tags tag.list` 59 | 我创建一个项目`buildtag-demo`,目录架构为 60 | ``` 61 | . 62 | ├── go.mod 63 | ├── main.go 64 | ├── tag_d.go 65 | └── tag_p.go 66 | ``` 67 | 我们在`main.go` 里头的代码是 68 | ```golang 69 | package main 70 | 71 | func main() { 72 | debug() 73 | } 74 | ``` 75 | 76 | `tag_d.go` 代表debug场景下要编译的文件,文件内容是 77 | ```golang 78 | // +build debug 79 | 80 | package main 81 | 82 | import "fmt" 83 | 84 | func debug() { 85 | fmt.Println("debug ") 86 | } 87 | ``` 88 | `tag_p.go` 代表生产环境下要编译的文件,文件内容是 89 | 90 | ```golang 91 | // +build !debug 92 | 93 | package main 94 | func debug() { 95 | //fmt.Println("debug ") 96 | } 97 | ``` 98 | 99 | 我们编译和运行下项目 100 | ```bash 101 | $ go build 102 | $ ./buildtag-demo 103 | //nothing 没有任何输出 104 | $ go build -tags debug 105 | $ ./buildtag-demo 106 | debug 107 | ``` 108 | 109 | #### 使用方式 110 | 111 | - `+build`注释需要在`package`语句之前,而且之后还需要一行空行。 112 | - `+build`后面跟一些条件 只有当条件满足的时候才编译此文件。 113 | - `+build`的规则不仅见约束`.go`结尾的文件,还可以约束go的所有源码文件。 114 | 115 | #### `+build`条件语法: 116 | - 只能是是数字、字母、_(下划线) 117 | - 多个条件之间,空格表示OR;逗号表示AND;叹号(!)表示NOT 118 | - 可以有多行`+build`,它们之间的关系是AND。如: 119 | ``` 120 | // +build linux darwin 121 | // +build 386 122 | 等价于 123 | // +build (linux OR darwin) AND 386 124 | ``` 125 | - 加上`// +build ignore`的原件文件可以不被编译。 126 | 127 | ## go get 128 | 129 | go get 在go modules出现之前一直作为go获取依赖包的工具,在go modules出现后,go get的功能和之前有了不一样的定位。现在go get主要的功能是获取解析并将依赖项添加到当前开发模块然后构建并安装它们。 130 | 131 | 参考 go modules 章节 132 | 133 | ## go还提供了其它很多的工具,例如下面的这些工具 134 | 135 | - go bug 给go官方提交go bug(执行命令后会在浏览器弹出go github issue提交页面) 136 | - go clean 这个命令是用来移除当前源码包和关联源码包里面编译生成的文件. 137 | - go doc 这个命令用来从包文件中提取顶级声明的首行注释以及每个对象的相关注释,并生成相关文档。 138 | - go env 这个命令我们之前介绍过,是用来获取go的环境变量 139 | - go fix 用于将你的go代码从旧的发行版迁移到最新的发行版,它主要负责简单的、重复的、枯燥无味的修改工作,如果像 API 等复杂的函数修改,工具则会给出文件名和代码行数的提示以便让开发人员快速定位并升级代码。 140 | - go fmt 格式化go代码。 141 | - go generate 这个命令用来生成go代码文件,平常工作中比较少接触到。 142 | - go install 编译生产可执行文件,同时将可执行文件移到$GOBIN这个环境变量设置的目录下 143 | - go list 查看当前项目的依赖模块(包),在go modules 小节会看到一些具体用法 144 | - go mod go的包管理工具,go modules会单独介绍 145 | - go run 编译和运行go程序 146 | - go test go的单元测试框架,会在go test小节单独介绍 147 | - go tool 这个命令聚合了很多go工具集,主要关注 go tool pprof 和 go tool cgo这两个命令,在go 性能调优和cgo的章节会讲到这两个命令。 148 | - go version 查看go当前的版本 149 | - go vet 用来分析当前目录的代码是否正确。 150 | 151 | 152 | 153 | # 参考资料 154 | 155 | - [go build -tags 试验](https://www.jianshu.com/p/858a0791f618) -------------------------------------------------------------------------------- /src/go项目管理/go项目管理.md: -------------------------------------------------------------------------------- 1 | # go项目管理 2 | 3 | 现代语言都会有包管理器,集成单元测试功能。golang最早的包依赖管理基于gopath,最近几年慢慢淡化gopath,基于go mod。 4 | 基于go mod,golang慢慢完善自己的包管理器。 5 | 6 | golang提供标准库`testing`,开发者可以非常方便的写出单元测试和基准测试代码。 7 | 8 | -------------------------------------------------------------------------------- /src/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/1.png -------------------------------------------------------------------------------- /src/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/2.png -------------------------------------------------------------------------------- /src/img/2pc1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/2pc1.png -------------------------------------------------------------------------------- /src/img/2pc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/2pc2.png -------------------------------------------------------------------------------- /src/img/CAP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/CAP.png -------------------------------------------------------------------------------- /src/img/asm1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/asm1.png -------------------------------------------------------------------------------- /src/img/concurreat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/concurreat.jpg -------------------------------------------------------------------------------- /src/img/docker-swarm1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/docker-swarm1.png -------------------------------------------------------------------------------- /src/img/docker-swarm2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/docker-swarm2.png -------------------------------------------------------------------------------- /src/img/docker-swarm3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/docker-swarm3.png -------------------------------------------------------------------------------- /src/img/docker-swarm4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/docker-swarm4.png -------------------------------------------------------------------------------- /src/img/docker1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/docker1.png -------------------------------------------------------------------------------- /src/img/docker2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/docker2.png -------------------------------------------------------------------------------- /src/img/docker3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/docker3.png -------------------------------------------------------------------------------- /src/img/go_learningmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/go_learningmap.png -------------------------------------------------------------------------------- /src/img/godebug-diagram1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/godebug-diagram1.png -------------------------------------------------------------------------------- /src/img/godebug-diagram2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/godebug-diagram2.png -------------------------------------------------------------------------------- /src/img/godebug-diagram3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/godebug-diagram3.png -------------------------------------------------------------------------------- /src/img/godebug-diagram4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/godebug-diagram4.png -------------------------------------------------------------------------------- /src/img/godebug-diagram5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/godebug-diagram5.png -------------------------------------------------------------------------------- /src/img/ide1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/ide1.png -------------------------------------------------------------------------------- /src/img/ide2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/ide2.png -------------------------------------------------------------------------------- /src/img/ide3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/ide3.png -------------------------------------------------------------------------------- /src/img/ide4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/ide4.png -------------------------------------------------------------------------------- /src/img/map1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/map1.png -------------------------------------------------------------------------------- /src/img/map2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/map2.png -------------------------------------------------------------------------------- /src/img/map3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/map3.png -------------------------------------------------------------------------------- /src/img/profile-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/profile-1.png -------------------------------------------------------------------------------- /src/img/profile-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/profile-2.png -------------------------------------------------------------------------------- /src/img/raft-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/raft-1.png -------------------------------------------------------------------------------- /src/img/raft-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/raft-2.png -------------------------------------------------------------------------------- /src/img/raft-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/raft-3.png -------------------------------------------------------------------------------- /src/img/raft-4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/raft-4.gif -------------------------------------------------------------------------------- /src/img/raft-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/raft-4.png -------------------------------------------------------------------------------- /src/img/raft-5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/raft-5.gif -------------------------------------------------------------------------------- /src/img/raft-6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/raft-6.gif -------------------------------------------------------------------------------- /src/img/raft-7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/raft-7.gif -------------------------------------------------------------------------------- /src/img/scheduler-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/scheduler-1.png -------------------------------------------------------------------------------- /src/img/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/wechat.png -------------------------------------------------------------------------------- /src/img/wechatgroup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/wechatgroup.png -------------------------------------------------------------------------------- /src/img/yamux1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/yamux1.png -------------------------------------------------------------------------------- /src/img/yamux2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widaT/learning-go/dddebbab0e7c3813fc5fd1685bb91d7edd4b553b/src/img/yamux2.png -------------------------------------------------------------------------------- /src/profiling/profiling.md: -------------------------------------------------------------------------------- 1 | # Go 性能分析(Profiling) 2 | 3 | Go语言提供了强大的性能分析工具,可以帮助我们分析和优化程序性能。本章将详细介绍几种主要的性能分析方法。 4 | 5 | ## 1. CPU Profiling 6 | 7 | CPU Profiling 用于分析程序的 CPU 使用情况,帮助我们找出程序中最耗费 CPU 的部分。 8 | 9 | ### 使用方法 10 | 11 | 有两种主要的方式来进行 CPU 性能分析: 12 | 13 | #### 1.1 通过代码方式 14 | 15 | ```go 16 | import ( 17 | "runtime/pprof" 18 | "os" 19 | "log" 20 | "flag" 21 | ) 22 | 23 | func main() { 24 | // 定义 CPU Profile 的输出文件 25 | cpuprofile := flag.String("cpuprofile", "", "write cpu profile to file") 26 | flag.Parse() 27 | 28 | if *cpuprofile != "" { 29 | f, err := os.Create(*cpuprofile) 30 | if err != nil { 31 | log.Fatal("could not create CPU profile: ", err) 32 | } 33 | defer f.Close() 34 | 35 | if err := pprof.StartCPUProfile(f); err != nil { 36 | log.Fatal("could not start CPU profile: ", err) 37 | } 38 | defer pprof.StopCPUProfile() 39 | } 40 | 41 | // ... 你的程序代码 ... 42 | } 43 | ``` 44 | 45 | #### 1.2 通过 HTTP 服务 46 | 47 | ```go 48 | import ( 49 | "net/http" 50 | _ "net/http/pprof" // 只需要引入包 51 | ) 52 | 53 | func main() { 54 | // 启动一个 HTTP 服务 55 | go func() { 56 | log.Println(http.ListenAndServe(":6060", nil)) 57 | }() 58 | 59 | // ... 你的程序代码 ... 60 | } 61 | ``` 62 | 63 | 启动程序后,可以通过以下方式获取 profile: 64 | ```bash 65 | go tool pprof http://localhost:6060/debug/pprof/profile # 默认30秒的CPU profile 66 | ``` 67 | 68 | ## 2. 内存分析(Memory Profiling) 69 | 70 | 内存分析用于查看程序的内存使用情况,帮助发现内存泄漏和优化内存使用。 71 | 72 | ### 使用方法 73 | 74 | #### 2.1 通过代码方式 75 | 76 | ```go 77 | import ( 78 | "runtime/pprof" 79 | "os" 80 | "runtime" 81 | ) 82 | 83 | func main() { 84 | // 记录内存profile 85 | f, err := os.Create("mem.prof") 86 | if err != nil { 87 | log.Fatal("could not create memory profile: ", err) 88 | } 89 | defer f.Close() 90 | 91 | runtime.GC() // 运行GC获取最新的内存统计 92 | if err := pprof.WriteHeapProfile(f); err != nil { 93 | log.Fatal("could not write memory profile: ", err) 94 | } 95 | } 96 | ``` 97 | 98 | #### 2.2 通过 HTTP 服务 99 | 100 | 使用与 CPU profile 相同的 HTTP 服务,访问不同的端点: 101 | ```bash 102 | go tool pprof http://localhost:6060/debug/pprof/heap 103 | ``` 104 | 105 | ## 3. Goroutine 分析 106 | 107 | Goroutine 分析可以帮助我们了解程序中 goroutine 的运行状况。 108 | 109 | ### 使用方法 110 | 111 | 通过 HTTP 服务查看当前的 goroutine 信息: 112 | ```bash 113 | go tool pprof http://localhost:6060/debug/pprof/goroutine 114 | ``` 115 | 116 | ## 4. 阻塞分析(Block Profiling) 117 | 118 | 阻塞分析用于发现程序中 goroutine 阻塞的位置。 119 | 120 | ### 使用方法 121 | 122 | ```go 123 | import "runtime" 124 | 125 | func main() { 126 | // 开启阻塞分析 127 | runtime.SetBlockProfileRate(1) // 设置采样率 128 | 129 | // ... 你的程序代码 ... 130 | } 131 | ``` 132 | 133 | 然后通过 HTTP 服务查看: 134 | ```bash 135 | go tool pprof http://localhost:6060/debug/pprof/block 136 | ``` 137 | 138 | ## 5. 互斥锁分析(Mutex Profiling) 139 | 140 | 互斥锁分析用于发现程序中互斥锁的竞争情况。 141 | 142 | ### 使用方法 143 | 144 | ```go 145 | import "runtime" 146 | 147 | func main() { 148 | // 开启互斥锁分析 149 | runtime.SetMutexProfileFraction(1) // 设置采样率 150 | 151 | // ... 你的程序代码 ... 152 | } 153 | ``` 154 | 155 | 通过 HTTP 服务查看: 156 | ```bash 157 | go tool pprof http://localhost:6060/debug/pprof/mutex 158 | ``` 159 | 160 | ## 6. 分析数据的可视化 161 | 162 | pprof 工具提供了多种可视化方式来查看性能分析数据: 163 | 164 | ### 6.1 终端交互模式 165 | 166 | ```bash 167 | go tool pprof [profile_file] 168 | ``` 169 | 170 | 在交互模式中,可以使用以下命令: 171 | - `top`:显示最耗时的函数列表 172 | - `list <函数名>`:显示函数的代码以及每行的耗时 173 | - `web`:在浏览器中查看调用图 174 | 175 | ### 6.2 生成火焰图 176 | 177 | 从 Go 1.11 开始,pprof 已经内置了火焰图支持: 178 | 179 | ```bash 180 | go tool pprof -http=:8080 [profile_file] 181 | ``` 182 | 183 | 在浏览器中访问后,可以切换到火焰图视图查看性能数据。 184 | 185 | ## 7. 最佳实践 186 | 187 | 1. **分析时机的选择** 188 | - 开发环境:在开发新功能时进行性能分析 189 | - 测试环境:在压测时收集性能数据 190 | - 生产环境:定期收集性能数据,但要注意性能开销 191 | 192 | 2. **采样率的设置** 193 | - CPU Profile:通常使用默认设置即可 194 | - 内存 Profile:建议在需要时开启,避免持续开启 195 | - 阻塞和互斥锁分析:根据需求调整采样率 196 | 197 | 3. **数据分析建议** 198 | - 关注 `top` 命令显示的高耗时函数 199 | - 使用火焰图分析调用链 200 | - 对比优化前后的性能数据 201 | 202 | 4. **注意事项** 203 | - 不同类型的性能分析可能会相互影响 204 | - 在生产环境中要谨慎使用,建议采用采样方式 205 | - 定期清理性能分析数据文件 -------------------------------------------------------------------------------- /src/profiling/内存分析实战.md: -------------------------------------------------------------------------------- 1 | # Go内存分析实战 2 | 3 | 在Go程序开发中,内存问题是常见的性能瓶颈之一。本文将通过实际案例,介绍如何使用pprof工具来分析和解决内存相关的问题。 4 | 5 | ## 内存泄漏示例 6 | 7 | 让我们先看一个典型的内存泄漏示例: 8 | 9 | ```go 10 | package main 11 | 12 | import ( 13 | "net/http" 14 | _ "net/http/pprof" 15 | "runtime" 16 | "time" 17 | ) 18 | 19 | // 模拟内存泄漏 20 | var cache = make(map[int][]byte) 21 | 22 | func addToCache() { 23 | for i := 0; ; i++ { 24 | // 每次分配1MB内存 25 | data := make([]byte, 1024*1024) 26 | cache[i] = data 27 | // 模拟业务处理 28 | time.Sleep(time.Millisecond * 100) 29 | } 30 | } 31 | 32 | func main() { 33 | // 开启pprof 34 | go func() { 35 | http.ListenAndServe(":6060", nil) 36 | }() 37 | 38 | // 打印初始内存状态 39 | printMemStats() 40 | 41 | // 启动内存泄漏的goroutine 42 | go addToCache() 43 | 44 | // 定期打印内存状态 45 | for { 46 | time.Sleep(time.Second * 10) 47 | printMemStats() 48 | } 49 | } 50 | 51 | func printMemStats() { 52 | var m runtime.MemStats 53 | runtime.ReadMemStats(&m) 54 | printf("Alloc = %v MiB", bToMb(m.Alloc)) 55 | printf("TotalAlloc = %v MiB", bToMb(m.TotalAlloc)) 56 | printf("Sys = %v MiB", bToMb(m.Sys)) 57 | printf("NumGC = %v\n", m.NumGC) 58 | } 59 | 60 | func bToMb(b uint64) uint64 { 61 | return b / 1024 / 1024 62 | } 63 | 64 | func printf(format string, args ...interface{}) { 65 | format = time.Now().Format("2006-01-02 15:04:05 ") + format + "\n" 66 | printf(format, args...) 67 | } 68 | ``` 69 | 70 | 这个程序模拟了一个常见的内存泄漏场景:持续向一个全局map中添加数据,但从不删除。运行这个程序,你会发现内存使用量持续增长。 71 | 72 | ## 使用pprof分析内存问题 73 | 74 | ### 1. 查看实时内存状态 75 | 76 | 程序运行后,可以通过浏览器访问 `http://localhost:6060/debug/pprof/heap` 查看内存分配情况。 77 | 78 | ### 2. 下载heap profile进行分析 79 | 80 | ```bash 81 | # 下载heap profile 82 | go tool pprof http://localhost:6060/debug/pprof/heap 83 | 84 | # 进入交互式界面后,可以使用以下命令 85 | (pprof) top # 查看占用内存最多的函数 86 | (pprof) list main # 查看main包相关的内存分配 87 | ``` 88 | 89 | ### 3. 生成内存分配火焰图 90 | 91 | ```bash 92 | go tool pprof -http=:8081 [heap_profile_file] 93 | ``` 94 | 95 | 在浏览器中访问 `http://localhost:8081/ui/flamegraph` 查看内存分配的火焰图。 96 | 97 | ## 常见内存问题及解决方案 98 | 99 | 1. **内存泄漏** 100 | - 症状:内存使用持续增长,不释放 101 | - 常见原因: 102 | * 全局变量(map、slice等)无限增长 103 | * goroutine泄漏 104 | * 未关闭的文件句柄或网络连接 105 | - 解决方案: 106 | * 使用sync.Map或带缓存淘汰的map 107 | * 合理设置超时机制 108 | * 使用defer确保资源释放 109 | 110 | 2. **内存占用过高** 111 | - 症状:程序内存使用远超预期 112 | - 常见原因: 113 | * 不合理的对象预分配 114 | * 频繁的大对象分配 115 | * 过多的临时对象创建 116 | - 解决方案: 117 | * 使用对象池(sync.Pool) 118 | * 减少不必要的对象创建 119 | * 合理设置切片容量 120 | 121 | 3. **频繁GC** 122 | - 症状:GC占用过多CPU时间 123 | - 常见原因: 124 | * 创建过多临时对象 125 | * 内存分配频繁 126 | - 解决方案: 127 | * 减少对象分配 128 | * 使用buffer池 129 | * 适当调整GOGC值 130 | 131 | ## 最佳实践 132 | 133 | 1. **定期监控** 134 | - 集成prometheus + grafana监控内存使用 135 | - 设置合理的内存告警阈值 136 | 137 | 2. **性能优化** 138 | - 在开发阶段就注意内存使用 139 | - 压测时关注内存增长趋势 140 | - 定期进行性能分析 141 | 142 | 3. **代码审查** 143 | - 关注资源释放相关代码 144 | - 检查全局变量的使用 145 | - 注意goroutine的生命周期 146 | 147 | ## 总结 148 | 149 | 内存问题的排查和优化是一个循环往复的过程: 150 | 151 | 1. 发现问题(监控告警) 152 | 2. 收集数据(pprof) 153 | 3. 分析问题(火焰图等工具) 154 | 4. 优化代码 155 | 5. 验证效果 156 | 157 | 通过熟练使用Go提供的性能分析工具,结合实际的业务场景,我们可以更好地发现和解决内存相关的性能问题。 -------------------------------------------------------------------------------- /src/profiling/火焰图.md: -------------------------------------------------------------------------------- 1 | # go 调优之火焰图 2 | 3 | 火焰图(flame graphs)是一种程序函数调用栈深度和耗时比例直观可交互展示的工具。 4 | 5 | 一图胜千言 6 | 7 | ![FlameGraph](../img/profile-1.png) 8 | 9 | 如上图展示,火焰图展示程序中被频繁调用的函数和耗时占比。这边展示的是一章截图,原图是svg是可交互的,比如我们想具体看某个函数中子函数的情况你可以点击那个函数。结果如下 10 | 11 | ![FlameGraph](../img/profile-2.png) 12 | 13 | 在go生态中,早期我们经常会用[go-torch](https://github.com/uber-archive/go-torch)来生成这样的图。从go1.11后 go tool pprof 就已经集成了这个功能,非常方便我们使用。 14 | 15 | ## 演示代码 16 | 17 | ```go 18 | package main 19 | 20 | import ( 21 | "encoding/json" 22 | "fmt" 23 | "net/http" 24 | _ "net/http/pprof" 25 | "time" 26 | ) 27 | 28 | func fn1() { 29 | b := fn2() 30 | fmt.Println(fn3(b)) 31 | } 32 | 33 | func fn2() (b []byte) { 34 | b, _ = json.Marshal(map[string]int{ 35 | "a": 22, 36 | "bb": 333, 37 | }) 38 | return 39 | } 40 | 41 | func fn3(b []byte) int { 42 | var m map[string]int 43 | json.Unmarshal(b, &m) 44 | 45 | if len(m) > 0 { 46 | return m["a"] 47 | } 48 | return 0 49 | } 50 | 51 | func main() { 52 | go func() { 53 | for { 54 | fn1() 55 | time.Sleep(1e8) 56 | } 57 | }() 58 | 59 | panic(http.ListenAndServe(":8080", nil)) 60 | } 61 | ``` 62 | 63 | 如上面代码所示,需要在代码中引入`_ "net/http/pprof"` 然后需要起一个http服务 `http.ListenAndServe(":8080", nil)`,就这样比较少的代码侵入。 64 | 65 | 运行 66 | 67 | ``` 68 | $ go run main.go 69 | ``` 70 | 71 | 然后你可以在浏览器中访问,http://127.0.01:8080/debug/pprof/ 72 | 73 | 看到如下的信息 74 | ``` 75 | /debug/pprof/ 76 | 77 | Types of profiles available: 78 | Count Profile 79 | 1 allocs 80 | 0 block 81 | 0 cmdline 82 | 5 goroutine 83 | 1 heap 84 | 0 mutex 85 | 0 profile 86 | 7 threadcreate 87 | 0 trace 88 | ... 89 | ``` 90 | ok 测试程序没问题了 91 | 92 | 93 | ## 生成火焰图 94 | 95 | ### step 1 96 | 97 | 运行如下的命令,下载profile文件 98 | 99 | ``` 100 | go tool pprof http://127.0.0.1:8080/debug/pprof/profile -seconds 5 101 | ``` 102 | 103 | ### step 2 104 | 105 | 运行如下命令生成火焰图 106 | ``` 107 | go tool pprof -http=:8081 ~/pprof/pprof.main.samples.cpu.001.pb.gz 108 | ``` 109 | 110 | `注意:这个时候可能会提升缺少 graphviz ubuntu和debian 使用 apt-get install graphviz 安装` 111 | 112 | 用浏览器访问 `http://localhost:8081/ui/flamegraph`这个时候就能看到火焰图了。 113 | 114 | 115 | ## 调优过程 116 | 117 | 我们生成火焰图的目录是为了直观的发现程序中最耗时的函数,然后想办法去优化它, 118 | 优化后继续生成火焰图看是否有效果,然后再去发现下一个最耗时的函数,重复上面的步骤直到整个程序达到你满意的性能。 -------------------------------------------------------------------------------- /src/web服务/Go-web服务.md: -------------------------------------------------------------------------------- 1 | # Go web服务 2 | -------------------------------------------------------------------------------- /src/web服务/go-web服务.md: -------------------------------------------------------------------------------- 1 | # Go web服务 2 | -------------------------------------------------------------------------------- /src/web服务/http请求参数校验.md: -------------------------------------------------------------------------------- 1 | # http请求参数校验 2 | 3 | 在web服务开发的过程中,我们经常需要对用户传递的参数进行验证。验证的代码很容易写得冗长,而且比较丑陋。本文我们介绍一个第三放库[validator](https://github.com/go-playground/validator)专门解决这个问题。 4 | 5 | ## 验证单一变量 6 | ```golang 7 | import ( 8 | "fmt" 9 | 10 | "gopkg.in/go-playground/validator.v9" 11 | ) 12 | func validateVariable() { 13 | myEmail := "someone.gmail.com" 14 | errs := validate.Var(myEmail, "required,email") 15 | if errs != nil { 16 | fmt.Println(errs) 17 | return 18 | } 19 | //这边验证通过 写逻辑代码 20 | } 21 | var validate *validator.Validate 22 | func main() { 23 | validate = validator.New() 24 | validateVariable() 25 | } 26 | ``` 27 | 运行一下 28 | ```bash 29 | $ go run main.go 30 | Key: '' Error:Field validation for '' failed on the 'email' tag 31 | ``` 32 | 33 | ## 验证结构体 34 | ```golang 35 | package main 36 | 37 | import ( 38 | "fmt" 39 | 40 | "gopkg.in/go-playground/validator.v9" 41 | ) 42 | 43 | type User struct { 44 | Name string `validate:"required"` 45 | Age uint8 `validate:"gte=0,lte=150"` //大于等于0 小于等于150 46 | Email string `validate:"required,email"` 47 | } 48 | 49 | var validate *validator.Validate 50 | func main() { 51 | validate = validator.New() 52 | validateStruct() 53 | } 54 | 55 | func validateStruct() { 56 | user := &User{ 57 | Name: "wida", 58 | Age: 165, 59 | Email: "someone.gmail.com", 60 | } 61 | 62 | err := validate.Struct(user) 63 | fmt.Println(err) 64 | if err != nil { 65 | if _, ok := err.(*validator.InvalidValidationError); ok { 66 | fmt.Println(err) 67 | return 68 | } 69 | for _, err := range err.(validator.ValidationErrors) { //变量所有参数错误 70 | fmt.Println(err.Namespace()) 71 | fmt.Println(err.Field()) 72 | fmt.Println(err.StructNamespace()) 73 | fmt.Println(err.StructField()) 74 | fmt.Println(err.Tag()) 75 | fmt.Println(err.ActualTag()) 76 | fmt.Println(err.Kind()) 77 | fmt.Println(err.Type()) 78 | fmt.Println(err.Value()) 79 | fmt.Println(err.Param()) 80 | fmt.Println() 81 | } 82 | return 83 | } 84 | } 85 | ``` 86 | 87 | 运行一下 88 | ```bash 89 | $ go run main.go 90 | Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag 91 | Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag 92 | User.Age 93 | Age 94 | User.Age 95 | Age 96 | lte 97 | lte 98 | uint8 99 | uint8 100 | 165 101 | 150 102 | 103 | User.Email 104 | Email 105 | User.Email 106 | Email 107 | email 108 | email 109 | string 110 | string 111 | someone.gmail.com 112 | ``` 113 | 运行的结果显示,`age`和`email`验证通不过。 -------------------------------------------------------------------------------- /src/web服务/web服务框架.md: -------------------------------------------------------------------------------- 1 | # web服务框架 2 | 3 | 一般情况下,如果只是很少的api,或者功能需求非常简单,我们通常不建议使用web框架。但是如果api众多,功能复杂,那么选用合适的一个web框架,对项目的帮助是非常大的。 4 | 5 | golang的生态中有好多个非常流行的web服务框架: 6 | 7 | - [beego](https://github.com/astaxie/beego) 8 | - [echo](https://github.com/labstack/echo) 9 | - [iris](https://github.com/kataras/iris) 10 | - [gin](https://github.com/gin-gonic/gin) 11 | 12 | 这些框架的核心功能都大同小异,会有自己定义的`router`,自己定义的各种web组件或者中间件。 13 | 14 | 一般我们选定一款web框架后会研究它的实现,方便自己在实现项目运用中排查问题。 15 | 16 | -------------------------------------------------------------------------------- /src/微服务/Go-micro微服务框架.md: -------------------------------------------------------------------------------- 1 | # Go-micro微服务框架 2 | -------------------------------------------------------------------------------- /src/微服务/微服务.md: -------------------------------------------------------------------------------- 1 | # 微服务 2 | 3 | ## 什么是微服务 4 | 5 | 注:来自维基百科 6 | 微服务 (Microservices) 是一种软件架构风格,它是以专注于单一责任与功能的小型功能区块为基础,利用模块化的方式组合出复杂的大型应用程序,各功能区块使用与语言无关 的 API 集相互通信。 7 | 8 | 微服务的规划与单体式应用程序十分不同,微服务中每个服务都需要避免与其他服务有所牵连,且都要能够自主,并在其他服务发生错误时不受干扰。其实这也SOA(面向服务编程)的理念非常类似,应该说微服务不算一个全新的一个概念,只是这几年随着容器技术的成熟,微服务的实践越来越方便。 9 | 10 | ## 微服务特点 11 | 12 | 注:来自维基百科 13 | 一个微服务框架的应用程序有下列特性: 14 | - 每个服务都容易被取代。 15 | - 服务是以能力来组织的,例如用户界面、前端、推荐系统、账单或是物流等。 16 | - 由于功能被拆成多个服务,因此可以由不同的编程语言、数据库实现。 17 | - 架构是对称而非分层(即生产者与消费者的关系)。 18 | - 一个微服务框架: 19 | - 适用于具持续交付 (Continuous Delivery) 的软件开发流程。 20 | - 与服务导向架构 (Service-Oriented Architecture) 不同,后者是集成各种业务的应用程序,但微服务只属于一个应用程序。 21 | 22 | 23 | ## 微服务的技术核心 24 | 25 | - 服务发现(Service Discovery): 26 | 27 | 微服务上线后只需要在服务注册中心,注册自己ip和服务内容,并不需要想整个集群广播自己的。当某个服务要调用某个服务时会向注册中心询问该服务的ip地址。服务注册中心保留了集群的服务到ip的映射关系,同时还会检查每个服务的健康状态,及时摘除故障服务或节点。 28 | 29 | - 容器技术 30 | 31 | 单体运营拆分成微服务后,部署和伸缩都会比单体运用复杂很多,所以说没有容器技术,微服务还是很难盛行的。 32 | 33 | 容器技术特点: 34 | - 轻量:相比于虚拟机计算。容器技术非常轻量,有更快的启动时间。 35 | - 易于部署迁移:由于环境一致性,所以能做到一次构建,处处部署; 36 | - 弹性伸缩:Kubernetes、docker Swarm等容器编排技术,可以做到弹性伸缩。 37 | 38 | 39 | ## go和微服务 40 | 41 | 当前践行微服务的主流编程语言只有go和java,go由于容器话技术的友好支持,成为微服务的首选语言。 42 | 43 | go的微服务生态也很齐全,服务发现 (consun,etcd),通讯协议(go-grpc),微服务框架(go-micro,go-kit)等等都非常成熟。 44 | 45 | # 参考资料 46 | 47 | - [维基百科](https://zh.wikipedia.org/wiki/%E5%BE%AE%E6%9C%8D%E5%8B%99) -------------------------------------------------------------------------------- /src/绪论.md: -------------------------------------------------------------------------------- 1 | # 后端程序员为什么选择go? 2 | 3 | ### 后端程序员为什么选择go? 4 | 5 | - 语法简单,入门快 6 | - 编译型语言,性能高,语言原生支持并发对后端程序员很有吸引力 7 | - 开发效率高,对于写api的后端程序员来说,golang的开发效率非常接近python,php等脚本语言 8 | - 社区活跃,大牛项目多,高质量的代码都可以任意阅读 (etcd,docker,k8s) 9 | 10 | ### 先看一个demo 11 | 12 | ```go 13 | package main 14 | import ( 15 | "io" 16 | "net/http" 17 | ) 18 | func helloHandler(w http.ResponseWriter, req *http.Request) { 19 | io.WriteString(w, "hello, world!\n") 20 | } 21 | 22 | func main() { 23 | http.HandleFunc("/", helloHandler) 24 | http.ListenAndServe(":8888", nil) 25 | } 26 | ``` 27 | 28 | ``` 29 | $ go run main.go 30 | ``` 31 | 32 | 这个demo程序实现一个weh服务,浏览器(或者curl)访问 http://localhost:8888/ 会显hello, world 33 | 34 | 现在来用ab工具来做下基准测试,100个client请求1w次的结果(环境 deepin golang 1.12.1 no nginx)。 35 | ``` 36 | $ ab -n 10000 -c 100 http://localhost:8888/ 37 | ``` 38 | ### 结果 39 | 40 | ``` 41 | Concurrency Level: 100 42 | Time taken for tests: 0.425 seconds 43 | Complete requests: 10000 44 | Failed requests: 0 45 | Total transferred: 1310000 bytes 46 | HTML transferred: 140000 bytes 47 | Requests per second: 23556.40 [#/sec] (mean) 48 | Time per request: 4.245 [ms] (mean) 49 | Time per request: 0.042 [ms] (mean, across all concurrent requests) 50 | Transfer rate: 3013.56 [Kbytes/sec] received 51 | 52 | Connection Times (ms) 53 | min mean[+/-sd] median max 54 | Connect: 0 2 0.4 2 4 55 | Processing: 1 2 0.6 2 6 56 | Waiting: 0 2 0.6 2 5 57 | Total: 2 4 0.5 4 8 58 | 59 | Percentage of the requests served within a certain time (ms) 60 | 50% 4 61 | 66% 4 62 | 75% 4 63 | 80% 4 64 | 90% 5 65 | 95% 5 66 | 98% 6 67 | 99% 6 68 | 100% 8 (longest request) 69 | ``` 70 | 71 | 现在用php7.2试一下同样的程序效果,比一下(环境 deepin linux nginx1.13.12 100 static php7.2-fpm) 72 | 73 | ``` 74 | $ ab -n 10000 -c 100 http://localhost/index.php 75 | ``` 76 | 77 | ### 代码 78 | index.php 79 | ```php 80 |