WIP:《100 Go Mistakes and How to Avoid Them》之并发基础篇

2023-07-12 ⏳1.7分钟(0.7千字)

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代表一个具体的时间点:

func (h publishHandler) publishPosition(position flight.Position) error { 
    ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
    defer cancel()
    return h.pub.Publish(ctx, position)
}

context.WithTimeout内部启动一个定时器协程,在4s之后调用cancel().而我们需要defer cancel()的原因是这个函数很可能极短时间就结束,我们手动调用就可以提前释放协程资源。 ### 8.6.2 取消信号取消信号是可以传递给子协程的。

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    go func() {
        CreateFileWatcher(ctx, "foo.txt")
    }()
    // ... 
}

当我们结束mian()函数,CreateFileWatcher收到取消信号 ### 8.6.2 上下文内容比较常用的一个功能就是携带上下文信息:

ctx := context.WithValue(context.Background(), "key", "value")
fmt.Println(ctx.Value("key"))
value

比如我们全局的traceID,或者一些用户信息。 ### 8.6.2 捕获取消信息 context.Context导出了一个Done方法接收一个channel信息<-chan struct{}. 当context.WithCancelcancel()被调用或者context.WithDeadline时间到了时,Donechannel就会接收到信息。与之对应的就是两个内部错误:context.Canceledcontext.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()
        }
    } 
}