WIP:《100 Go Mistakes and How to Avoid Them》之并发基础篇
礼物说8.1 混淆并发和并行
并发和并行很容易搞混,我们再讲并发之前,先了解下他们的概念,文章举了咖啡馆的例子:当我们有一个咖啡馆,单线程的时候就是这样: 当我们人流量变大后,一个方案就是 再加一个服务员和一台机器,这就是
并行
: 另一种方案就是,我们可以把接订单的服务员和放咖啡豆的服务员区分开,这样用户也能分开,这就是
并发
:
我们可以从中总结出,并发其实提供了一套结构去解决问题有可能也是并行的。 Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.
并行是同时做很多事情,而并发是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了。
8.2 认为并发就一定更快
很多人都认为并发就一定比单线程快,这其实是大错特错。本章我们看下Go基础的并发知识,让我们重新认知这个问题。 ### 8.2.1 Go调度器 ### 8.2.1 并行合并排序
8.3 不清楚什么使用使用channels或者mutexes
8.4 不理解竞态问题
8.5 不理解并发带来的负载类型
面对不同的负载类型(CPU密集型 or IO密集型),我们的处理方式应该也不同。
8.6 错误理解Go contexts
很多开发者会错误理解context.Context
。本章可以看下如何使用以及更高效的使用。以下是context.Context
官方概念。
A Context carries a deadline, a cancellation signal, and other values across API boundaries.
8.6.1 Deadline
Deadline
代表一个具体的时间点:
- 从现在开始的一段
time.Duration
- 某个具体的时间点
time.Time
表示某个活动会在这个时间点到了之后停止。
func (h publishHandler) publishPosition(position flight.Position) error {
, cancel := context.WithTimeout(context.Background(), 4*time.Second)
ctxdefer cancel()
return h.pub.Publish(ctx, position)
}
context.WithTimeout
内部启动一个定时器协程,在4s之后调用cancel()
.而我们需要defer cancel()
的原因是这个函数很可能极短时间就结束,我们手动调用就可以提前释放协程资源。 ### 8.6.2 取消信号取消信号是可以传递给子协程的。
func main() {
, cancel := context.WithCancel(context.Background())
ctxdefer cancel()
go func() {
(ctx, "foo.txt")
CreateFileWatcher}()
// ...
}
当我们结束mian()
函数,CreateFileWatcher
收到取消信号 ### 8.6.2 上下文内容比较常用的一个功能就是携带上下文信息:
:= context.WithValue(context.Background(), "key", "value")
ctx .Println(ctx.Value("key"))
fmt value
比如我们全局的traceID
,或者一些用户信息。 ### 8.6.2 捕获取消信息 context.Context
导出了一个Done
方法接收一个channel信息<-chan struct{}
. 当context.WithCancel
的cancel()
被调用或者context.WithDeadline
时间到了时,Done
channel就会接收到信息。与之对应的就是两个内部错误:context.Canceled
和context.DeadlineExceeded
。使用方式:
func handler(ctx context.Context, ch chan Message) error {
for {
select {
case msg := <-ch:
// Do something with msg
case <-ctx.Done():
return ctx.Err()
}
}
}