Go 并发编程:goroutine 与 channel 的工程实践
Go 的并发不是"额外特性",而是语言核心。从 CSP(Communicating Sequential Processes)模型出发,Go 把并发做成第一公民——go 关键字、channel 类型、select 语句都是语法级支持,不像其他语言要靠线程库。
但"易用"不意味着"易对"。goroutine 泄露、channel 死锁、竞态条件(race condition)这些坑,写 Go 三年依然会遇到。本文总结工程中的并发模式与避坑要点。
《教父》三部曲:一部关于权力、家族与命运的黑帮史诗
第一次看完《教父》三部曲,往往会有一种久坐不动的沉重感——一个黑帮题材的系列片,竟然能带来读完一整部家族史小说的体验。这种感受在后来看过的诸多黑帮片里,再难找到第二次。
从 1972 年的第一部,到 1974 年的第二部,再到 1990 年的第三部,弗朗西斯·福特·科波拉用了将近二十年时间,把马里奥·普佐小说里的西西里移民家族故事,拍成了美国电影史上最具史诗气质的系列之一。其中《教父》获得奥斯卡最佳影片、最佳男主角、最佳改编剧本三项大奖,《教父 II》更进一步拿下六项奥斯卡——它至今仍是第一部也是唯一一部获得最佳影片的续集电影1。
PHP 7 zval 底层结构重构与垃圾回收机制
PHP 7 相比 PHP 5,最重要的性能优化之一就是 zval 的重新设计。zval 是 PHP 中一切变量的底层容器,理解它的内存模型对于理解 PHP 的内存管理、引用语义和垃圾回收至关重要。本文从 PHP 5/7 对比出发,逐步剖析 PHP 7 zval 的设计。
PHP 5 的 zval:臃肿的设计
PHP 5 中,zval 结构体大致如下:
struct _zval_struct {
zvalue_value value; // 16 字节,存储实际值
zend_uint refcount__gc; // 4 字节,引用计数
zend_uchar type; // 1 字节,类型
zend_uchar is_ref__gc; // 1 字节,是否为引用
};整体 24 字节(在 64 位对齐后实际占 32 字节)。每个值变量本身就带 refcount 和 is_ref 标记。这意味着:
- 每个值都要分配 refcount 计数:小字符串、小整数也跑不掉
- 每个 zval 都需要单独内存分配:栈上的临时变量没法直接复用
- 复杂值才需要的概念,简单值也被迫承担
对于 PHP 这种"一切皆值"的语言,这种设计的内存开销显著。
Golang goroutine 调度器 GMP 模型深度剖析
Go 语言的一大卖点是用 go func() 就能起一个 goroutine,开发者几乎不需要关心底层调度。但要写出高性能的 Go 程序,理解 GMP 调度模型是必经之路。本文从设计目标出发,逐步拆解 GMP 的工作机制。
goroutine vs 线程
要理解 GMP,首先要明确 goroutine 与 OS 线程的差异:
| 维度 | OS 线程 | goroutine |
|---|---|---|
| 占用内存 | 1-8 MB(栈 + 内核结构) | 2 KB(初始栈) |
| 创建/销毁 | 需要内核参与(系统调用) | 用户态完成,开销极低 |
| 调度 | 内核抢占式 | Go runtime 协作式 + 抢占 |
| 数量上限 | 几千(受系统资源限制) | 数十万甚至百万 |
goroutine 的轻量源自用户态调度——Go runtime 自己实现调度器,不依赖内核线程切换。
GMP 三种对象
Go 调度器涉及三种核心对象:
graph TB
subgraph G[Goroutine]
G1[g1]
G2[g2]
G3[g3]
G4[g4]
G5[g5]
end
subgraph P[Logical Processor]
P1[p1]
P2[p2]
P3[p3]
P4[p4]
end
subgraph M[OS Thread Machine]
M1[m1]
M2[m2]
M3[m3]
end
P1 --- M1
P2 --- M2
P3 --- M3
G1 -.running on.-> M1
G2 -.queued in.-> P1
G3 -.queued in.-> P2
G4 -.queued in.-> P3
G5 -.global queue.-> GSQ[(Global Run Queue)]- G(Goroutine):用户态协程,保存运行栈、状态、任务函数
- M(Machine):OS 线程,由 Go runtime 管理(实际对应系统线程)
- P(Processor):逻辑处理器,维护本地 G 队列;M 必须绑定 P 才能执行 G
每个 P 同一时刻只能被一个 M 持有。M 的数量可以大于 P(P 默认数量 = GOMAXPROCS)。P 决定了 Go 程序能并行使用的 CPU 核数。
HTTPS TLS 1.2 握手过程与加密原理全解析
HTTPS = HTTP + TLS(早期叫 SSL)。TLS 在传输层之上、应用层之下,提供加密、完整性校验和身份认证三大能力。本文聚焦 TLS 1.2(当前仍为主流的版本),从密码学原语到完整握手逐步剖析。
三大密码学原语
TLS 用到三类基本工具:
- 对称加密(如 AES、ChaCha20):加密实际传输的数据,速度快
- 非对称加密(如 RSA、ECDSA):用于密钥交换和身份认证,效率低
- 哈希 + MAC(如 SHA-256、HMAC):保证数据完整性,防止篡改
TLS 的精妙之处在于用非对称加密安全地协商出一个对称密钥,之后的通信都用对称加密完成——兼顾安全和性能。
TLS 1.2 完整握手流程
一个完整的 TLS 1.2 RSA 密钥交换握手需要 2-RTT(两次往返):
sequenceDiagram
participant C as Client
participant S as Server
C->>S: 1. ClientHello<br/>TLS 版本、随机数、加密套件列表
S->>C: 2. ServerHello<br/>选定加密套件、随机数<br/>Certificate (服务器证书链)<br/>ServerHelloDone
C->>S: 3. ClientKeyExchange<br/>用服务器公钥加密 Pre-Master Secret<br/>ChangeCipherSpec<br/>Finished (MAC 校验)
S->>C: 4. ChangeCipherSpec<br/>Finished (MAC 校验)
Note over C,S: 握手完成,后续用对称密钥加密通信第一步:ClientHello
客户端发送: