谈谈Go语言宏观理解之二 - 并发系统 | 青训营笔记

158 阅读3分钟

在 Go 语言官方文档中读到了 Go 语言设计理念的内容,讲的十分全面。计划用系列文章整理一下对Go语言的宏观理解。主要是翻译自原名为 Go History 的文章:Frequently Asked Questions (FAQ) - The Go Programming Language: go.dev/doc/faq

先列一下文章链接:

这篇文章主要梳理一下Go语言并发系统的新特点。

并发系统

并发性能是各种服务器程序和编程语言不断追求的宏伟目标。从过去到现在,工程实践中广泛使用的Java语言一直寻求将“轻量化线程”加入到成熟的Java体系中。以Fiber为代表的JDK开发项目组已经推出了测试版JDK,可惜要在生产中广泛使用的Java8中享受到轻量化线程的利好还遥遥无期。而Go语言作为“云原生语言”,设计之初就考虑到了轻量化线程的语言特性。

基于 CSP 思想构建并发系统

并发和多线程编程长久以来都被公认具有很高难度。我们认为部分原因是 pthreads 等类库的复杂设计,以及 mutex(互斥锁)、条件变量、内存屏障等过于强调底层细节所致。更高层次的接口能够带来更简单的代码,即使在幕后仍然存在互斥锁等。

为并发提供高级语言支持的最成功模型之一来自 Hoare 的通信顺序过程(CSP)。Occam 和 Erlang 是两种源自 CSP 的著名语言。Go 的并发原语同样来自于 CSP 技术分支,采用通道支持进程间通信。在早期语言上的经验表明,CSP 通信模型非常适合面向过程的语言框架。

使用 Goroutine 代替线程

Goroutine 是使得 Go 语言并发易于使用的重要部分。其背后的协程思想已经流行了较长一段时间,核心思想就是在一组有限的线程之上复用众多独立执行单元(协程)。当一个协程由于阻塞式系统调用等原因阻塞时,Go runtime 自动把同一个操作系统线程上的其他协程切换到操作系统的其他可运行线程上,以避免一个协程阻塞导致其他协程也阻塞。更关键的部分在于程序员看不到协程背后的复杂机制。这使得 goroutine 极其轻量化:除去栈内存之外,内存开销只有几千字节。

为使占内存占用更小,Go runtime 采用了可调整大小的有界栈结构。新创建的 goroutine 通常只需要分配几千字节的空间:多数情况下都是足够的。如果发现内存不够,Go runtime 会自动调整用于储存协程栈的大小,让大量协程占用适量内存。平均用于内存管理的函数调用仅占用 3 个 CPU 指令左右。实践中,在同一个地址空间可以创建数十万协程。如果仅仅采用操作系统线程的话,系统资源早已用尽。