Ardan Labs Go Training 拆解:机械同情 + 数据导向设计 + 一致性哲学
💬 评论逐章解析 ardanlabs/gotraining:47MB / 474 个 Go 文件 / 82 篇 Markdown 的 Go 深度教学仓库。从机械同情到 channel 信号语义,从指针语义到 interface 多态,从 fan-out 模式到 scheduler 行为,配 15 张原图 + 8 个经典样例
📘 项目信息
- 项目名:ardanlabs/gotraining(Ultimate Go 课程教材)
- 作者:William Kennedy(@goinggodotnet),Ardan Labs
- 代码量:47 MB / 474 个 Go 文件 / 82 篇 Markdown
- 仓库地址:https://github.com/ardanlabs/gotraining ↗
- 课程地址:https://www.ardanlabs.com/training ↗
- 配套书:《Ultimate Go Notebook》
- 许可证:Apache 2.0(每个示例文件顶部都有声明)
- 课程形式:3 天密集班(Ultimate Go),目标受众是有几个月到几年 Go 经验的中级开发者
一、为什么这个仓库值得啃一遍?#
Go 圈有两个”事实标准”的进阶教学来源——ardanlabs/gotraining 和 go101.org。前者偏工业实战、后者偏语言规范细节。
Ardan Labs 的 Ultimate Go 课程已经讲过 8 年(从 2014 年 William Kennedy 创立 Ardan Labs 算起),教过”几千名开发者”,教材在全世界 Go 圈是公开资源。这种被反复打磨过的课程有 3 个特质:
- 每一行代码都注释了”为什么”——不是”这样写能跑”,而是”这样写体现了语言设计哲学的哪一条”
- 样例按”递增复杂度”组织——
example1→example2→ …,每加一个特性不引入新的复杂度 - 设计准则放在第一位——每章节开头都是 Design Guidelines,先告诉你”应该怎么想”,再给代码
本文按仓库的章节结构做一次完整拆解,重点是:
- 机械同情(Mechanical Sympathy):为什么 Go 的设计是为硬件服务的?
- Channel 信号语义:别把 channel 当队列
- 指针语义与内存布局:为什么 Go 的指针比 C++ 的安全,比 Java 的透明
- Scheduler 行为:M:N 调度的真相
- Interface 多态:Go 的鸭子类型为什么”刚好够用”
- 错误处理哲学:error 是值,不是异常
- fan-out / pool / cancellation 模式:工业并发代码模板
二、仓库结构#
gotraining/
├── topics/
│ ├── go/ # 核心教学材料(Ultimate Go 课程主线)
│ │ ├── language/ # 18 章:变量 / 指针 / 数组 / slice / map / 接口 ...
│ │ ├── concurrency/ # goroutine / channel / pattern / data race
│ │ ├── design/ # composition / error_handling / packaging
│ │ ├── profiling/ # pprof / trace / godebug / mutex / block
│ │ ├── testing/ # tests / benchmark / fuzzing
│ │ ├── packages/ # context / io / encoding / reflection / logging
│ │ ├── algorithms/ # simulations (pi / sha256sum)
│ │ ├── exercises/ # chatbot / sha256sum / contributors
│ │ ├── generics/ # Go 1.18+ 泛型
│ │ └── videos/ # 配套视频索引
│ └── courses/go/ # 课程编排(README 链接)
├── reading/ # 推荐阅读清单
├── env/ # 课程环境配置(vim + zsh + tmux)
├── tools/ # 教学用辅助脚本
├── vendor/ # 依赖快照(教学环境离线可跑)
├── go.mod / go.sum
├── Opening.md # 课程开场白(必读)
└── README.mdplaintext每个 language / concurrency / design 子目录都是自包含的——有 README.md 讲解 + example1 / example2 … 递进 + exercises 巩固 + advanced 进阶。仓库设计得非常清晰,任何人可以挑一节单读。
三、机械同情:课程的第一性原理#
“We focus on teaching how to write code with a priority on consistency, integrity, readability and simplicity.” —— README
课程反复回到 4 个核心价值,按重要性排序:
- Readability(可读):人是代码的主要读者,不是机器
- Simplicity(简单):用最直接的方式写
- Consistency(一致性):整个 codebase 一种风格
- Integrity(完整性):靠编译器和测试守护
这 4 个价值再向下贯穿就是机械同情(Mechanical Sympathy)——写代码时脑子里要有硬件的图。

💡 小吴笔记:CPU 访问 L1 cache ~1 ns、L3 ~30 ns、主存 ~100 ns。L1 miss 一次等于”白白浪费” 100 倍时间。Go 的 slice / struct 布局设计就是冲着”让数据贴近”去的。
机械同情在课程里表现为几个具体原则:
- 值类型优先于引用类型:避免堆分配 → 减少 GC 压力 → 数据更靠近 cache
- 小 struct 不要逃逸到堆:编译器会自动栈分配;用
go build -gcflags="-m"能看到逃逸分析结果 - 数据局部性 > 抽象层次:宁愿一个 struct 字段多,不要为了”内聚”嵌套多层
- 零值有用:
bytes.Buffer{}/sync.Mutex{}/sync.WaitGroup{}都可直接用,不要写”假构造器”
四、Channel 信号语义:别当队列用#
课程里最重要的一章就是 channels。它反复强调一句话:
Don’t architect software with the idea of channels being a queue, focus on signaling and the semantics that simplify the orchestration required.
4.1 三张决策图#
选 channel 前先问自己:
问题 1:是否需要”送达保证”?

- 需要 1:1 送达保证:unbuffered channel(同步握手)
- 允许 1:N 通知(fire-and-forget):buffered channel with capacity ≥ N + close
- 不需要送达:nil channel / select default 分支(drop 模式)
问题 2:传数据还是传信号?


- 带数据:channel type 是
chan T,send/receive 都是值的拷贝(指针 / interface / struct 都可以) - 不带数据:channel type 是
chan struct{},专门用来 cancel / 通知,零内存开销
问题 3:channel 当前 state 是什么?

nil:send / receive 都永久阻塞;用在 select 里等于”禁用这条路”open:正常收发closed:send panic,receive 立即返回零值 +ok=false
💡 小吴笔记:用
nil channel在 select 里”禁用”是 Go 的一个标志性 idiom,比if分支判断读起来干净很多:goselect { case ch <- data: // 当 ch 是 nil,整条 case 永远阻塞 case <-ctx.Done(): }
4.2 经典样例:basic mechanics(example1)#
样例文件 topics/go/concurrency/channels/example1/example1.go 把 6 种基础模式摆在一起,注释详细到每行函数的作用:
// waitForResult: 父 goroutine 等待子 goroutine 完成工作并传回结果
func waitForResult() {
ch := make(chan string) // unbuffered,1:1 同步握手
go func() {
time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)
ch <- "data"
fmt.Println("child : sent signal")
}()
d := <-ch // 阻塞直到 child 写入
fmt.Println("parent : recv'd signal :", d)
}go// fanOut: 父 goroutine 创建 2000 个子 goroutine,等待它们都返回
func fanOut() {
children := 2000
ch := make(chan string, children) // buffered,容量 = children,避免 send 阻塞
for c := 0; c < children; c++ {
go func(child int) {
time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)
ch <- "data"
fmt.Println("child : sent signal :", child)
}(c)
}
for children > 0 {
d := <-ch
children--
fmt.Println("parent : recv'd signal :", children)
}
}go// pooling: 父 goroutine 把 100 个任务分发到一个 worker pool
func pooling() {
ch := make(chan string)
g := runtime.GOMAXPROCS(0) // 取逻辑 processor 数
for c := 0; c < g; c++ {
go func(child int) {
for d := range ch { // 循环接收直到 channel close
fmt.Printf("child %d : recv'd signal : %s\n", child, d)
}
fmt.Printf("child %d : recv'd shutdown signal\n", child)
}(c)
}
const work = 100
for w := 0; w < work; w++ {
ch <- "data"
}
close(ch) // 关键!没有 close,所有 worker 永远阻塞在 range
}go// cancellation: 父 goroutine 最多等 150ms,超过就放弃
func cancellation() {
duration := 150 * time.Millisecond
ctx, cancel := context.WithTimeout(context.Background(), duration)
defer cancel()
ch := make(chan string, 1)
go func() {
time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)
ch <- "data"
}()
select {
case d := <-ch:
fmt.Println("work complete", d)
case <-ctx.Done():
fmt.Println("work cancelled")
}
}go// fanOutSem: 给 fan-out 加信号量,限制同时跑的子 goroutine 数
func fanOutSem() {
children := 2000
ch := make(chan string, children)
g := runtime.GOMAXPROCS(0)
sem := make(chan bool, g) // 信号量:buffered channel 当 mutex 用
for c := 0; c < children; c++ {
go func(child int) {
sem <- true // 获取令牌
{
t := time.Duration(rand.Intn(200)) * time.Millisecond
time.Sleep(t)
ch <- "data"
fmt.Println("child : sent signal :", child)
}
<-sem // 释放令牌
}(c)
}
// ... 接收部分同上 ...
}go// drop: 当子 goroutine 处理不过来时,主动丢弃任务
func drop() {
const cap = 100
ch := make(chan string, cap)
go func() {
for p := range ch {
fmt.Println("child : recv'd signal :", p)
}
}()
const work = 2000
for w := 0; w < work; w++ {
select {
case ch <- "data":
fmt.Println("parent : sent signal :", w)
default:
fmt.Println("parent : dropped data :", w) // 满了就丢,不要阻塞父
}
}
close(ch)
}go💡 小吴笔记:注意几个工程细节——
make(chan string, children)的容量 = 孩子数,保证没有 send 阻塞。这是 Go 并发的标准做法。for d := range ch是 receive 的惯用法——不用手写for;;,close 后自动退出。select { case ch <- data: ... default: ... }是非阻塞 send 的标准 idiom,满了就走 default。sem := make(chan bool, g)—— buffered channel 当信号量用是 Go 的标志性 idiom,比sync.Mutex更适合这种”限制并发数”的场景。
4.3 进阶:bounded work pooling#
// boundedWorkPooling: pool of workers service a fixed amount of work
func boundedWorkPooling() {
work := []string{"paper", "paper", "paper", "paper", 2000: "paper"} // 2004 个任务
g := runtime.GOMAXPROCS(0)
var wg sync.WaitGroup
wg.Add(g)
ch := make(chan string, g)
for c := 0; c < g; c++ {
go func(child int) {
defer wg.Done()
for wrk := range ch {
fmt.Printf("child %d : recv'd signal : %s\n", child, wrk)
}
}(c)
}
for _, wrk := range work {
ch <- wrk
}
close(ch)
wg.Wait()
}go4.4 Buffer Bloat:课程里一节意外”硬”的提醒#
## Buffer Bloat - 2011
* Large buffers prevent timely notification of back pressure.
* They defeat your ability to reduce back pressure in a timely matter.
* They can increase latency not reduce it.
* Use buffered channels to provide a way of maintaining continuity.
* Don't use them just for performance.
* Use them to handle well defined bursts of data.
* Use them to deal with speed of light issues between handoffs.markdown来源是 2011 年 Jim Gettys 关于网络 buffer 的论文,这是 Go channel 设计的核心动机——不是”为了让数据快”,是”为了在确定的边界处理 well-defined bursts”。
五、Goroutine 与 Scheduler#
5.1 三张核心图#
Goroutine 调度器:

Go 用 M:N 调度——M 个 goroutine 跑在 N 个 OS thread 上(N 一般等于 GOMAXPROCS),runtime 自己维护一个 logical processor + run queue。
Unbuffered channel 语义:

Send 阻塞直到另一端 Receive;Receive 阻塞直到另一端 Send。同步握手。
Buffered channel 语义:

Send 不阻塞直到 buffer 满;Receive 不阻塞直到 buffer 空。
5.2 经典样例:scheduler behavior(example1)#
// topics/go/concurrency/goroutines/example1/example1.go
func init() {
// 关键:只给 scheduler 1 个 logical processor
// 这样能看清楚 goroutine 之间的协作/调度细节
runtime.GOMAXPROCS(1)
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
fmt.Println("Start Goroutines")
go func() {
lowercase()
wg.Done()
}()
go func() {
uppercase()
wg.Done()
}()
fmt.Println("Waiting To Finish")
wg.Wait()
fmt.Println("\nTerminating Program")
}go💡 小吴笔记:课程里特别提——
runtime.GOMAXPROCS(1)是教学技巧,让你看清两个 goroutine 怎么”轮流”用唯一一个 processor。生产环境跑 N=CPU 核数即可。
六、指针语义:基础中的基础#
6.1 第一性:值语义(pass by value)#
// topics/go/language/pointers/example1/example1.go
func main() {
count := 10
println("count:\tValue Of[", count, "]\tAddr Of[", &count, "]")
increment(count) // 传"值",函数拿到的是拷贝
println("count:\tValue Of[", count, "]\tAddr Of[", &count, "]")
// ↑ count 还是 10,因为 increment 改的是它的拷贝
}
func increment(inc int) {
inc++
println("inc:\tValue Of[", inc, "]\tAddr Of[", &inc, "]")
// ↑ inc 是 11,但地址不同(栈帧不同)
}go输出:
count: Value Of[ 10 ] Addr Of[ 0xc0000160a0 ]
inc: Value Of[ 11 ] Addr Of[ 0xc0000160d0 ]
count: Value Of[ 10 ] Addr Of[ 0xc0000160a0 ]plaintext6.2 三条指针准则(从课程抽出)#
- Go 是值语义语言——所有赋值 / 参数传递都是 copy
- 指针共享的是地址,不是数据——指针本身也是值,是某个变量的地址
- 指针让函数能修改”调用方的数据”——把地址传过去,函数能 deref 改原值
6.3 GC 算法示意#

Go 用三色标记 + 写屏障实现并发 GC。课程没深入 GC 实现,但给了这张图让你记住:GC 会 stop-the-world 一小段时间(虽然现在已经压到 sub-millisecond),所以才有”减少堆分配”的工程建议。
七、Slice:最容易踩坑的内置类型#
7.1 length vs capacity#
// topics/go/language/slices/example1/example1.go
func main() {
// 创建一个 length = 5 的 slice
fruits := make([]string, 5)
fruits[0] = "Apple"
fruits[1] = "Orange"
fruits[2] = "Banana"
fruits[3] = "Grape"
fruits[4] = "Plum"
// 不能访问 length 之外的 index
fruits[5] = "Runtime error" // panic: runtime error: index out of range
}go💡 小吴笔记:
make([]string, 5)和make([]string, 0, 5)不一样。前者 length=5 capacity=5;后者 length=0 capacity=5。前者你能直接读 index 0-4,后者你不能读任何 index(即使 capacity 够)。这是新手最常踩的坑。
7.2 Slice 的三参数切法(slice 课程的核心)#
// 经典 idiom
func copySlice(src []int) []int {
dst := make([]int, len(src)) // 先按 length 分配
copy(dst, src)
return dst
}
// 切片共享底层 array
s := []int{1, 2, 3, 4, 5}
a := s[1:3] // a == [2, 3],但底层 array 还是同一个
a[0] = 99 // s[1] 也变成 99!go八、Interface 多态:刚好够用#
8.1 经典样例:reader interface#
// topics/go/language/interfaces/example1/example1.go
// reader 是接口,定义"读"这个动作
type reader interface {
read(b []byte) (int, error)
}
// file 实现 reader
type file struct {
name string
}
func (file) read(b []byte) (int, error) {
s := "<rss><channel><title>Going Go Programming</title></channel></rss>"
copy(b, s)
return len(s), nil
}
// pipe 也实现 reader
type pipe struct {
name string
}
func (pipe) read(b []byte) (int, error) {
s := `{name: "bill", title: "developer"}`
copy(b, s)
return len(s), nil
}
func main() {
f := file{"data.json"}
p := pipe{"cfg_service"}
retrieve(f)
retrieve(p)
}
// retrieve 能处理任何实现了 reader 的类型
func retrieve(r reader) error {
data := make([]byte, 100)
len, err := r.read(data)
if err != nil {
return err
}
fmt.Println(string(data[:len]))
return nil
}go8.2 课程的”接口设计准则”#
接口属于消费者,不是生产者。
这跟 Java 的”接口属于生产者”完全相反。意思是:
- 你的
file/pipe类型不知道有reader接口的存在 - 接口是
retrieve函数自己定义的,说自己”需要一个能 read 的东西” file/pipe只要方法签名匹配就算”实现”了接口(鸭子类型)
这个设计带来两个工程优势:
- 解耦:
file/pipe不需要 import 接口所在的包 - 避免接口膨胀:接口只在需要的地方定义,永远小(通常 1-3 个方法)
九、错误处理:error 是值#
9.1 error 接口#
// topics/go/design/error_handling/example1/example1.go
// 标准库的 error 接口(重新展示一下)
type error interface {
Error() string
}
// 标准库的 errorString 类型
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
// New 构造一个 error
func New(text string) error {
return &errorString{text}
}
func main() {
if err := webCall(); err != nil {
fmt.Println(err)
return
}
fmt.Println("Life is good")
}
func webCall() error {
return New("Bad Request")
}go9.2 错误处理的 5 条准则#
课程里反复强调的 error handling guidelines:
- error 是值,不是控制流 —— 不要用 panic 替代 error
- error 信息要给”上下文”,不要给”原因” ——
fmt.Errorf("webCall: %w", err)而不是errors.New(err.Error()) - 处理错误(不是返回错误)的函数才”拥有”错误 —— 业务逻辑里只决定怎么处理,不要盲目往上 return
- 哨兵错误用
errors.Is比较,类型化错误用errors.As取值 —— 不要用err == io.EOF这种字符串比较 - 不要忽略 error ——
_ = doSomething()几乎都是 bug,至少 log 一下
十、Composition:Go 的”OOP”#
Go 没有继承,只有组合。课程专门讲:
- Embedding(嵌入):通过嵌入 struct 复用字段和方法
- Interface satisfaction(接口满足):隐式的多态
- Decoupling(解耦):用接口把”做什么”和”怎么做”分开
样例在 topics/go/design/composition/example1/example1.go,从一个小 struct 嵌套、一层一层把”动物 → 狗 → 服务犬”演化成完整模型。这套教学非常工整——每加一层只引入一个概念。
十一、Profiling:会用 pprof 就够#
仓库 topics/go/profiling/ 章节覆盖:
| 子目录 | 主题 |
|---|---|
pprof | CPU / heap / goroutine profile |
trace | execution trace(看调度) |
memcpu | 内存 / CPU 行为 |
mutex | 锁竞争 profile |
blocking | 阻塞 profile |
stack_trace | 栈帧分析 |
http_trace | HTTP tracing |
godebug | go runtime/debug 包 |
bce | bounds check elimination(边界检查消除) |
project | 端到端实战 |
图 testing_coverage.png 展示了 Go test coverage 的可视化结果——每行代码被执行时会被标记:

跑 go test -cover 就能拿到覆盖率;-coverprofile=c.out && go tool cover -html=c.out 生成上图。
十二、Algorithms 与可视化#
topics/go/algorithms/simulations/pi/ 用蒙特卡洛模拟计算 π:

经典图——在单位正方形里随机撒点,落在单位圆里的比例 × 4 ≈ π。Go 实现可以一行 goroutine 跑 N 个模拟,结果 reduce。
// 简化版示意
func monteCarloPi(samples int) float64 {
inside := 0
for i := 0; i < samples; i++ {
x, y := rand.Float64(), rand.Float64()
if x*x+y*y < 1 {
inside++
}
}
return 4.0 * float64(inside) / float64(samples)
}gotopics/go/algorithms/simulations/sha256sum/ 演示并发 sha256 求和,11 张图的 benchmark visualization:


跑 go test -bench=. -cpuprofile=cpu.out,然后 go tool pprof -png cpu.out > figure1.png。
十三、Testing 三件套#
topics/go/testing/ 分三块:
- tests:单元测试、子测试、table-driven test
- benchmarks:
BenchmarkXxx函数、-benchmem、profile - fuzzing:Go 1.18+ 的
FuzzXxx函数,自动生成边界 case
样例测试函数都长这样:
func TestCalculate(t *testing.T) {
tests := []struct {
name string
input int
expected int
}{
{"zero", 0, 0},
{"one", 1, 1},
{"negative", -5, -25},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Calculate(tt.input); got != tt.expected {
t.Errorf("Calculate(%d) = %d, want %d", tt.input, got, tt.expected)
}
})
}
}goTable-driven test 是 Go 的标准 idiom——把测试 case 写成 slice,循环跑。比 Java 的 JUnit 5 / pytest 都紧凑。
十四、Generics(Go 1.18+)#
topics/go/generics/ 演示 Go 泛型:
// 一个泛型函数
func Map[T, U any](s []T, f func(T) U) []U {
r := make([]U, len(s))
for i, v := range s {
r[i] = f(v)
}
return r
}
// 使用
nums := []int{1, 2, 3}
doubled := Map(nums, func(n int) int { return n * 2 })
// doubled == []int{2, 4, 6}
strs := []string{"a", "b", "c"}
upper := Map(strs, func(s string) string { return strings.ToUpper(s) })
// upper == []string{"A", "B", "C"}goGo 泛型比 C++ template 简单得多——只支持类型参数 + type constraints(接口做约束),不支持模板元编程 / SFINAE。够用,不多给。
十五、推荐学习路径#
如果你打算认真读这份教材,我建议按这个顺序:
第 1 周 language/variables → language/constants → language/functions
(建立 Go 语法直觉)
第 2 周 language/pointers → language/struct_types → language/arrays
(理解值语义和内存布局)
第 3 周 language/slices → language/maps → language/embedding
(掌握内置容器 + 嵌入组合)
第 4 周 language/interfaces → language/methods
(多态与"接口属于消费者"哲学)
第 5 周 design/composition → design/error_handling → design/packaging
(从语法到设计哲学)
第 6 周 concurrency/goroutines → concurrency/channels
(最重要的一章)
第 7 周 concurrency/patterns → concurrency/data_race
(工业级模式 + race detector)
第 8 周 profiling/pprof → profiling/trace
(性能分析与调度行为)
第 9 周 testing/tests → testing/benchmarks → testing/fuzzing
(测试三件套)
第 10 周 generics → exercises/* → algorithms/*
(泛型 + 综合练习)plaintext**每天 1-2 个 example,不要跳。**仓库组织得很工整,按顺序读就是最对的学习曲线。
十六、跟我之前写的几篇 Go 解析的关系#
我之前写过:
- gin-v1-12 源码深潜——从零学 Web 框架
- go-redis v9 源码深潜——Redis 客户端
- grpc-go v1.84 源码深潜——gRPC 实现
- kratos v3 微服务架构——微服务框架
那几篇是**“框架源码”路线**——已经会 Go 之后,去看工业项目怎么写。这篇 gotraining 是**“语言本身”路线**——把 Go 的设计哲学吃透。两条路互补:
- 框架源码路:让你能看懂别人的项目、贡献开源
- 语言机制路:让你写代码时脑子里有硬件的图,知道为什么这么写
我建议先 gotraining,再框架源码——读完 gotraining 再去看 gin / grpc,你能看到设计哲学的连贯性(一致性、错误处理、机械同情贯穿所有工业项目)。
十七、致谢#
感谢 William Kennedy 和 Ardan Labs 把这套教材放在 GitHub 上。8 年迭代、上千学员反馈打过的教材——任何想进阶 Go 的人都不该错过。
This material is licensed under the Apache License Version 2.0, January 2004 http://www.apache.org/licenses/LICENSE-2.0 ↗
📅 本文状态:本文一次性写完,仓库已 clone / 分析 / 删除。如果 gotraining 后续有重大更新(例如 Go 1.24+ 的新章节),我再补一篇后续。
💬 讨论:你读完 gotraining 最有感的章节是哪个?channel 信号语义还是 pointer 内存布局?欢迎在评论区聊。
附录 A:仓库统计(截至 2026-08-13)#
| 指标 | 数量 |
|---|---|
| Go 文件 | 474(不含 vendor) |
| Markdown 文件 | 82 |
| 教学图片 | 17(本文保留 15 张) |
| topics 子目录 | 12 大类 |
| example* 样例目录 | ~140 |
| exercise 目录 | ~40 |
| 单文件最大行数 | ~250 行 |
| 单 README 最大字数 | ~5000 字 |
| 依赖包 | 标准库 + vendor 离线快照 |
附录 B:本文保留的图片清单#
| 图片 | 出处 | 用途 |
|---|---|---|
cache_latencies_graph.png | language/arrays | 机械同情 CPU 访问延迟 |
guarantee_of_delivery.png | concurrency/channels | channel 决策图 1 |
signaling_with_data.png | concurrency/channels | channel 决策图 2 |
signaling_without_data.png | concurrency/channels | channel 决策图 3 |
state.png | concurrency/channels | channel state 决策图 |
unbuffered.png | concurrency/channels | unbuffered 语义 |
buffered.png | concurrency/channels | buffered 语义 |
scheduler.png | concurrency/goroutines | M:N 调度器 |
data_race.png | concurrency/data_race | race condition |
figure1.png | language/arrays | 数组布局 |
GC_Algorithm.png | language/pointers | GC 三色标记 |
testing_coverage.png | testing/tests | 覆盖率可视化 |
figure1.png | testing/benchmarks | benchmark CPU profile |
figure2.png | testing/benchmarks | benchmark 火焰图 |
circle.png | algorithms/simulations | 蒙特卡洛 π |
全部图片保留在 /images/gotraining/ 路径下,可独立阅读。