Three-Day Thinking in Go (Day 3)
这是这个系列最后一天的笔记。
Concurrency
go语言有很好的并发。go语言的并发是依靠go routines,go routine是协程。
协程和线程最大的不同是,协程是协作式的,而线程是抢占式的。协程的开销也要比线程小很多。
Go Routines
- 轻量级线程
- 用函数来定义 (可以是有名字的,也可以是匿名的)
- GOMAXPROCS 默认值是当前cpu的core的数目,定义了并发度
- core是在thread中共享的
- 一个OS thread只能在一个core上(线程是cpu调度的最小单位)
- 触发线程reschedule的事件:sys call, channel(mutex), go func, GC
- GC是有成本的(用Java的人应该很明白)
- context switch是非常昂贵的
- goroutine有这些状态: Running, Sleeping, Waiting
- 需要想清楚你的goroutine什么时候开始和结束
1 | package main |
WaitGroup
- 是一种带计数器的信号量(semaphore)
- 跟踪记录在跑的goroutines
- 当WaitGroup计数为0的时候,会变的可用,否则则block在那里
- WaitGroup得传指针
WaitGroup的结构是
1 | // A WaitGroup waits for a collection of goroutines to finish. |
WaitGroup的state()
方法会返回counter和sema
1 | // state returns pointers to the state and sema fields stored within wg.state1. |
当WaitGroup call Add(delta int)
的时候,这个delta就会被加入到counter中
关键代码如下(中间有省略),
1 | statep, semap := wg.state() |
而 call Done()
则相当于
1 | func (wg *WaitGroup) Done() { |
而 Wati()
则相当于在一个for循环中block直到counter为0
Mutex
Mutex是Mutual exclusion的简写,叫做互斥锁
互斥锁保护起来的代码段叫做临界区域(critical section)
- 允许多个goroutine共享内存的机制
- 同步对共享状态的读写
- 代价比较低,且较快
- 状态为0的时候,表示可用:unlocked
- 调度器决定哪一个goroutine可以进入critical section
- RWMutex, 读写互斥锁,
- sync/atomic 了解一下,也可以用来做sycnhronization
Channels
Unbuffered
因为是unbuffered,所以是阻塞的。
- 专门为并发设计,为goroutine做编排
- 起到了发送信号的作用,可以有data也可以没有数据
- receive会先发生
- channel是双向的
- 保证了信号是一定能收到的
- 用关键字
make
来创建channel - 发
c<-
- 收
<-c
- 两个状态,打开/关闭,一旦关闭就不能重新打开
- 在null channel上发送/接受会阻塞
- <-c 会返回ok,通过ok的值来看channel是否被关闭
- 在声明的时候,如果可以,请尽可能的声明的channel的方向
- 可以用for range来读一个channel
1 | package main |
不一定需要发数据, 可以发一个空结构体
1 | package main |
Select
- 可以用select来在多个channel上收发数据
- select会block在那里,直到某个case触发
- 注意channel的方向
附上一个稍微复杂点的例子
这里有三个channel
- 一个申明的c,数据channel
- 一个声明的d,作为控制信道,空结构体,不传数据,
- 一个是timer,也是一个channel
chan func After(d Duration) <-chan Time
1 | package main |
If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the “select” statement blocks until at least one of the communications can proceed. - select
当有多个goroutine都在一个channel上的时候,当消息来的时候,是一个伪随机的均匀分布来选择一个goroutine来接收的。所以上一题,就取决于谁先收到,因为每个goroutine的处理时间和超时时间不一样,如果g1先收到 g2g3都会超时。
Buffered
- 对于producer不能保证一定delive
- 消息会被drop,如果接收方没了
- 适用于, fast producer / slow consumer
- 对于不同workload,需要通过测量你的throughput来确定一个合适的缓冲大小
JSON
- 标准库原生支持, encoding/decoding
- 小的json doc可以用json.Marshal/json.Unmarshal
- 大的json文件可以考虑 Decoder.Decode/Encode
- 可以通过
json:"name"
来改变生产的json的名字
1 | package main |
1 | package main |
Web Service
- go的标准库里就有http library
- 加上json库基本上就可以做出很好的web service
- 如果需要更复杂的routing和中间件,可以考虑第三方的web framework, go-kit, GorillaMux, go-chassis, buffalo, Gin等
- openapi/swagger 和相关的generator: go-swagger, openapi-generator
- 还有一个test库用来测试网络服务
- 记得要close reponse body。
Web Server
1 | type Pong struct { |
Http Client
1 | u, err := url.Parse("http://localhost:3000") |
Testing
1 | func TestPingHandler(t *testing.T) { |
结语
希望通过这三个短短的笔记,能够让读者对go有一个基本的认识,
当然要想进一步的提高,还是应该在工作项目和业余side project尽可能找到机会可以用go来编写程序。希望有机会等更深入的使用了这一门语言之后继续分享更多的经验。