goroutine 与并发初探
需要预先了解: 函数 包与模块:代码组织 goroutine 与并发初探 Go 的并发能力从语言层面就开始了:一个 go 关键字就能启动一个 goroutine。这篇先讲清楚"并发"与"并行"的区别、goroutine 的启动与开销,再引出 sync.WaitGroup、sync.Once 这两个基础同步工具,最后给出 GMP 调度模型的概览,为后面几篇铺路。 并发与并行 先区分两个概念: 并发(concurrency):多个任务在重叠的时间段内推进,宏观上"同时"发生,微观上可能是交替执行; 并行(parallelism):多个任务在同一个时刻真正同时执行,需要多核支持。 并发是一种程序结构,并行是一种执行状态。一个程序可以并发但不并行(比如只给一个 CPU 核心)。 几个系统层面的执行单元: 概念 说明 进程 拥有独立地址空间的执行单元,进程之间默认不共享内存 线程 进程内的执行单元,共享进程的地址空间,由操作系统调度 协程 用户态的轻量执行单元,由语言运行时或库调度...
channel
需要预先了解: goroutine 与并发初探 channel channel 是 goroutine 之间传递数据的管道,也是 Go 并发模型的核心。它同时承担两件事:传递数据和同步。这一篇讲清楚无缓冲与有缓冲的差别、close 的规则、select 的多路复用,以及几个会让程序直接崩溃的操作。 设计哲学 Go 的并发信条是: 不要通过共享内存来通信,而要通过通信来共享内存。 意思是:与其让多个 goroutine 抢着读写同一块内存(然后用锁保护),不如让数据在 goroutine 之间传过来,同一时刻只有一方持有它。channel 就是传递的载体。 无缓冲 channel:同步语义 make(chan T) 创建的是无缓冲 channel。它的发送和接收必须同时就位才能完成——发送方会一直阻塞,直到有接收方来取: ch := make(chan int) go func() { fmt.Println("发送前") ch <- 1 fmt.Println("发送完成") }() time.Sleep(100 * ti...
sync 包与原子操作
需要预先了解: goroutine 与并发初探 channel sync 包与原子操作 多个 goroutine 同时读写同一份数据,如果不加保护,就会产生竞态条件(race condition)。这一篇讲清楚竞态是怎么发生的、怎么用 -race 检测它,以及 sync.Mutex、sync.RWMutex、sync/atomic、sync.Map、sync.Pool 各自的适用场景。 竞态条件 var counter int func main() { var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() counter++ }() } wg.Wait() fmt.Println("counter =", counter) } 期望值是 1000,实际运行可能得到: counter = 968 原因在于 counter++ ...
context
需要预先了解: channel sync 包与原子操作 context 一个请求往往要派生多个 goroutine:查数据库、调下游服务、写日志。如果其中一步超时或客户端断开,其他步骤需要尽快停下。context 就是用来统一传递取消信号、截止时间和请求范围内的元数据的标准机制。 需求引入 假设一个函数要并发做三件事,任何一件失败或超时就整体放弃。如果每个 goroutine 各自用 done channel,代码会变成一堆手写的通知逻辑: done1 := make(chan struct{}) done2 := make(chan struct{}) // 谁来关闭?超时怎么办?取消要不要级联? context 把这些问题标准化:它是一棵可级联取消的树,父节点取消时所有子节点都会收到通知。 Context 接口 type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{...
并发设计模式与陷阱总结
需要预先了解: goroutine 与并发初探 channel sync 包与原子操作 context 并发设计模式与陷阱总结 前四篇讲了 goroutine、channel、同步原语和 context 的用法,这一篇把它们组织成可复用的模式,并把并发编程里最容易踩的坑集中列出来。 pipeline **pipeline(流水线)**把任务拆成若干阶段,每个阶段的输入输出都是 channel,数据像流水一样流过: // 生成器:产生数据 func gen(nums ...int) <-chan int { out := make(chan int) go func() { defer close(out) for _, n := range nums { out <- n } }() return out } // 阶段:处理数据 func square(in <-chan int) <-chan int { out ...



