mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-12 18:26:36 +08:00
wavelet init
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
# 高级并发模式
|
||||
|
||||
来自 Effective Go 的高级并发模式详细参考。这些模式适用于特定场景 —— 在需要请求/响应多路复用或 CPU 密集型并行化时使用。
|
||||
|
||||
---
|
||||
|
||||
## Channel 的 Channel
|
||||
|
||||
> **来源**:Effective Go
|
||||
|
||||
Channel 是一等公民值,可以像其他值一样被分配和传递。一个强大的模式是在请求结构体中嵌入 **回复 channel**,让每个客户端提供自己的应答路径:
|
||||
|
||||
```go
|
||||
type Request struct {
|
||||
args []int
|
||||
f func([]int) int
|
||||
resultChan chan int
|
||||
}
|
||||
```
|
||||
|
||||
客户端发送一个包含函数、参数和接收结果 channel 的请求:
|
||||
|
||||
```go
|
||||
request := &Request{[]int{3, 4, 5}, sum, make(chan int)}
|
||||
clientRequests <- request
|
||||
fmt.Printf("answer: %d\n", <-request.resultChan)
|
||||
```
|
||||
|
||||
服务端处理器从队列中读取请求,并将结果发送回每个请求的回复 channel:
|
||||
|
||||
```go
|
||||
func handle(queue chan *Request) {
|
||||
for req := range queue {
|
||||
req.resultChan <- req.f(req.args)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个模式构成了限速、并行、非阻塞 RPC 系统的基础,无需任何互斥锁。
|
||||
|
||||
---
|
||||
|
||||
## CPU 密集型并行化
|
||||
|
||||
> **来源**:Effective Go(现代化版本)
|
||||
|
||||
当计算可以分解为独立的部分时,使用 `sync.WaitGroup` 等待完成,将其并行化到多个 CPU 核心上:
|
||||
|
||||
```go
|
||||
type Vector []float64
|
||||
|
||||
func (v Vector) DoSome(i, n int, u Vector) {
|
||||
for ; i < n; i++ {
|
||||
v[i] += u.Op(v[i])
|
||||
}
|
||||
}
|
||||
|
||||
func (v Vector) DoAll(u Vector) {
|
||||
numCPU := runtime.NumCPU()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numCPU)
|
||||
for i := 0; i < numCPU; i++ {
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
v.DoSome(i*len(v)/numCPU, (i+1)*len(v)/numCPU, u)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
```
|
||||
|
||||
使用 `runtime.NumCPU()` 获取硬件核心数,或使用 `runtime.GOMAXPROCS(0)` 以遵循用户的资源配置。
|
||||
|
||||
> **重要**:不要混淆并发(将程序组织为独立执行的组件)和并行(在多个 CPU 上同时执行计算)。Go 是一门并发语言;并非所有并行化问题都适合它的模型。
|
||||
|
||||
---
|
||||
|
||||
## 常见错误
|
||||
|
||||
### 忘记通知完成
|
||||
|
||||
如果 goroutine 从未调用 `wg.Done()`(或从未在 done channel 上发送),等待的 goroutine 将永远阻塞:
|
||||
|
||||
```go
|
||||
// 不好:缺少 wg.Done —— 死锁
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
doWork()
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
// 好:始终 defer wg.Done
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
doWork()
|
||||
}()
|
||||
wg.Wait()
|
||||
```
|
||||
|
||||
### 无限制的 goroutine 创建
|
||||
|
||||
为每个工作项无限制地启动 goroutine 可能会耗尽内存或压垮下游资源。使用信号量来限制并发数:
|
||||
|
||||
```go
|
||||
// 不好:一次性创建 len(items) 个 goroutine
|
||||
var wg sync.WaitGroup
|
||||
for _, item := range items {
|
||||
wg.Add(1)
|
||||
go func(it Item) {
|
||||
defer wg.Done()
|
||||
process(it)
|
||||
}(item)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// 好:信号量将并发限制为 maxWorkers
|
||||
var wg sync.WaitGroup
|
||||
sem := make(chan struct{}, maxWorkers)
|
||||
for _, item := range items {
|
||||
wg.Add(1)
|
||||
sem <- struct{}{}
|
||||
go func(it Item) {
|
||||
defer wg.Done()
|
||||
defer func() { <-sem }()
|
||||
process(it)
|
||||
}(item)
|
||||
}
|
||||
wg.Wait()
|
||||
```
|
||||
@@ -0,0 +1,73 @@
|
||||
# 使用 Channel 的缓冲池
|
||||
|
||||
使用有缓冲 channel 作为空闲列表来复用已分配的缓冲区,避免重复分配。这种"泄漏缓冲"模式使用带 `default` 的 `select` 进行非阻塞操作。
|
||||
|
||||
> **来源**:Effective Go
|
||||
|
||||
```go
|
||||
var freeList = make(chan *Buffer, 100) // 有缓冲 channel 作为空闲列表
|
||||
|
||||
// 客户端:从空闲列表获取缓冲区或分配新的
|
||||
func getBuffer() *Buffer {
|
||||
select {
|
||||
case b := <-freeList:
|
||||
return b // 复用已有缓冲区
|
||||
default:
|
||||
return new(Buffer) // 空闲列表为空;分配新缓冲区
|
||||
}
|
||||
}
|
||||
|
||||
// 服务端:如有空间则将缓冲区归还空闲列表,否则丢弃
|
||||
func putBuffer(b *Buffer) {
|
||||
b.Reset() // 为重用做准备
|
||||
select {
|
||||
case freeList <- b:
|
||||
// 缓冲区已归还空闲列表
|
||||
default:
|
||||
// 空闲列表已满;丢弃缓冲区(GC 会回收)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 工作原理
|
||||
|
||||
1. **非阻塞接收**:客户端尝试从 `freeList` 获取缓冲区。如果为空,`default` 分支运行并分配新缓冲区。
|
||||
2. **非阻塞发送**:服务端尝试归还缓冲区。如果 `freeList` 已满,`default` 分支运行,缓冲区被丢弃等待垃圾回收。
|
||||
3. **有限内存**:channel 容量(100)限制了池中缓冲区的数量,防止无限增长。
|
||||
|
||||
当分配成本较高且缓冲区复用有益,但你不希望在池空或池满时出现阻塞行为时,这种模式非常有用。
|
||||
|
||||
## 何时使用
|
||||
|
||||
- 高频分配相似大小的对象
|
||||
- 分配开销影响性能的代码路径
|
||||
- 需要限制内存使用量的场景
|
||||
|
||||
## 生产环境替代方案
|
||||
|
||||
对于生产代码,考虑使用 `sync.Pool`,它提供类似功能并与垃圾收集器有更好的集成:
|
||||
|
||||
```go
|
||||
var bufferPool = sync.Pool{
|
||||
New: func() any {
|
||||
return new(Buffer)
|
||||
},
|
||||
}
|
||||
|
||||
func getBuffer() *Buffer {
|
||||
return bufferPool.Get().(*Buffer)
|
||||
}
|
||||
|
||||
func putBuffer(b *Buffer) {
|
||||
b.Reset()
|
||||
bufferPool.Put(b)
|
||||
}
|
||||
```
|
||||
|
||||
`sync.Pool` 的优势:
|
||||
- 垃圾收集期间自动清理
|
||||
- 无需管理池大小
|
||||
- 天生线程安全
|
||||
- 高并发下性能更好
|
||||
|
||||
基于 channel 的方式对于理解 Go 的并发原语以及需要更多控制池行为的场景仍然很有价值。
|
||||
@@ -0,0 +1,126 @@
|
||||
# Goroutine 生命周期模式
|
||||
|
||||
管理 goroutine 生命周期的详细模式 —— 确保每个 goroutine 都有清晰的启动/停止机制并防止资源泄漏。
|
||||
|
||||
---
|
||||
|
||||
## 使生命周期清晰
|
||||
|
||||
> WaitGroup 示例和作用域规则在父 skill(SKILL.md § Goroutine 生命周期,核心规则)中。本参考涵盖:stop/done channel 模式、等待策略、init() 生命周期示例和同步 API 设计。
|
||||
|
||||
---
|
||||
|
||||
## Stop/Done Channel 模式
|
||||
|
||||
每个 goroutine 必须有可预测的停止机制。使用 stop channel 通知关闭,使用 done channel 确认退出:
|
||||
|
||||
```go
|
||||
var (
|
||||
stop = make(chan struct{}) // 通知 goroutine 停止
|
||||
done = make(chan struct{}) // 通知我们 goroutine 已退出
|
||||
)
|
||||
go func() {
|
||||
defer close(done)
|
||||
ticker := time.NewTicker(delay)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
flush()
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 关闭时:
|
||||
close(stop) // 通知 goroutine 停止
|
||||
<-done // 并等待它退出
|
||||
```
|
||||
|
||||
在已关闭的 channel 上发送会 panic —— 始终使用 `close()` 来发信号,不要直接发送:
|
||||
|
||||
```go
|
||||
ch := make(chan int)
|
||||
close(ch)
|
||||
ch <- 13 // panic: 在已关闭的 channel 上发送
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 等待 Goroutine
|
||||
|
||||
> 多 goroutine 的 `sync.WaitGroup` 模式在父 skill 中(SKILL.md § Goroutine 生命周期)。以下是单 goroutine 的 done-channel 替代方案。
|
||||
|
||||
为单个 goroutine 使用 done channel:
|
||||
|
||||
```go
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
// 工作...
|
||||
}()
|
||||
<-done // 等待 goroutine 完成
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 不在 init() 中使用 Goroutine
|
||||
|
||||
> 核心规则在父 skill 中(SKILL.md § 核心规则,规则 3)。以下是展示生命周期管理的扩展示例。
|
||||
|
||||
```go
|
||||
// 不好:创建了不可控的后台 goroutine
|
||||
func init() {
|
||||
go doWork()
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// 好:显式的生命周期管理
|
||||
type Worker struct {
|
||||
stop chan struct{}
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func NewWorker() *Worker {
|
||||
w := &Worker{
|
||||
stop: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
go w.doWork()
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *Worker) Shutdown() {
|
||||
close(w.stop)
|
||||
<-w.done
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 优先使用同步函数
|
||||
|
||||
> 理由和优势表在父 skill 中(SKILL.md § 同步函数)。以下是具体的代码示例。
|
||||
|
||||
```go
|
||||
// 好:同步函数 - 调用方控制并发
|
||||
func ProcessItems(items []Item) ([]Result, error) {
|
||||
var results []Result
|
||||
for _, item := range items {
|
||||
result, err := processItem(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// 调用方可以在需要时添加并发:
|
||||
go func() {
|
||||
results, err := ProcessItems(items)
|
||||
// 处理结果
|
||||
}()
|
||||
```
|
||||
@@ -0,0 +1,110 @@
|
||||
# 同步原语模式
|
||||
|
||||
互斥锁和原子操作的详细模式 —— 涵盖互斥锁嵌入陷阱和类型安全的原子访问。
|
||||
|
||||
---
|
||||
|
||||
## 不要嵌入互斥锁
|
||||
|
||||
如果你通过指针使用结构体,互斥锁应该是非指针字段。不要在结构体中嵌入互斥锁,即使该结构体未被导出。
|
||||
|
||||
```go
|
||||
// 不好:嵌入的互斥锁将 Lock/Unlock 暴露为 API 的一部分
|
||||
type SMap struct {
|
||||
sync.Mutex // Lock() 和 Unlock() 成为 SMap 的方法
|
||||
data map[string]string
|
||||
}
|
||||
|
||||
func (m *SMap) Get(k string) string {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
return m.data[k]
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// 好:命名字段使互斥锁保持为实现细节
|
||||
type SMap struct {
|
||||
mu sync.Mutex
|
||||
data map[string]string
|
||||
}
|
||||
|
||||
func (m *SMap) Get(k string) string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return m.data[k]
|
||||
}
|
||||
```
|
||||
|
||||
在不好的示例中,`Lock` 和 `Unlock` 方法无意中成为了导出 API 的一部分。在好的示例中,互斥锁是对调用方隐藏的实现细节。
|
||||
|
||||
---
|
||||
|
||||
## 原子操作:完整示例
|
||||
|
||||
标准 `sync/atomic` 包操作原始类型(`int32`、`int64` 等),容易忘记一致地使用原子操作。
|
||||
|
||||
```go
|
||||
// 不好:容易忘记原子操作
|
||||
type foo struct {
|
||||
running int32 // 原子操作
|
||||
}
|
||||
|
||||
func (f *foo) start() {
|
||||
if atomic.SwapInt32(&f.running, 1) == 1 {
|
||||
return // 已在运行
|
||||
}
|
||||
// 启动 Foo
|
||||
}
|
||||
|
||||
func (f *foo) isRunning() bool {
|
||||
return f.running == 1 // 竞争!忘记使用 atomic.LoadInt32
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// 好:类型安全的原子操作
|
||||
type foo struct {
|
||||
running atomic.Bool
|
||||
}
|
||||
|
||||
func (f *foo) start() {
|
||||
if f.running.Swap(true) {
|
||||
return // 已在运行
|
||||
}
|
||||
// 启动 Foo
|
||||
}
|
||||
|
||||
func (f *foo) isRunning() bool {
|
||||
return f.running.Load() // 不可能意外地非原子读取
|
||||
}
|
||||
```
|
||||
|
||||
`atomic.Bool`、`atomic.Int64` 等类型(Go 1.19 起在标准库 `sync/atomic` 中可用,或通过 [go.uber.org/atomic](https://pkg.go.dev/go.uber.org/atomic))通过隐藏底层类型来增加类型安全性。
|
||||
|
||||
---
|
||||
|
||||
## Channel 方向示例
|
||||
|
||||
指定方向可以防止意外误用:
|
||||
|
||||
```go
|
||||
// 好:指定方向 - 清晰的所有权
|
||||
func sum(values <-chan int) int {
|
||||
total := 0
|
||||
for v := range values {
|
||||
total += v
|
||||
}
|
||||
return total
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// 不好:未指定方向 - 允许意外误用
|
||||
func sum(values chan int) (out int) {
|
||||
for v := range values {
|
||||
out += v
|
||||
}
|
||||
close(values) // 漏洞!这能通过编译但不应该发生。
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user