Files
actor/README.md
2026-10-20 18:53:09 +08:00

69 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Actor
每个 Actor 使用一个泛型 MPSC mailbox。队列不使用互斥锁或自旋锁。默认取得消费权的发送者在自己的 goroutine 中处理消息,直到 mailbox 清空,库不会创建后台 goroutine;也可以在构造时注入 dispatcher,将整个消费任务交给其他 goroutine 或已有线程池。
默认同步执行(inline):
```go
var total int
counter := actor.New(func(value int) {
total += value
}, actor.WithFixedCapacity[int](64), actor.WithBatchSize[int](64))
counter.Send(1) // 空闲时,在当前 goroutine 中处理。
```
- `New(handler, opts...)`:创建 Actor。处理函数或 option 为空、固定容量或批量大小不为正数、dispatcher 接口或 `DispatcherFunc` 为空时 panic。
- `Send(message)`:无返回值。发布消息后,取得消费权的发送者提交一次完整消费任务;已有任务排队或执行时仅入队。默认同步执行,返回不保证该消息已经处理,也不支持撤消。
- `WithFixedCapacity[T](n)`:预分配并保留 `n` 个节点,默认 64。固定区全部消费后循环复用;不足时分配动态节点,因此这不是队列总容量限制。
- `WithBatchSize[T](n)`:默认每消费 64 条消息调用 `runtime.Gosched()`。长链反转同样定期让出 CPU;单条处理函数不会被这个限制中断。
- `WithPanicHandler[T](func(message T, value any, stack []byte))`:可选 panic 回调。每条处理函数及回调自身的 panic 独立恢复,之后继续处理后续消息;没有回调时也继续处理。
- `WithDispatcher[T](Dispatcher)`:在构造时配置消费任务的执行方式,不提供运行中切换。调度策略不改变批量让出 CPU 的频率。
使用 `DispatcherFunc` 将消费任务交给其他 goroutine:
```go
processed := make(chan int, 1)
receiver := actor.New(func(value int) {
processed <- value
}, actor.WithDispatcher[int](actor.DispatcherFunc(func(task func()) {
go task()
})))
receiver.Send(42)
value := <-processed // 用业务自己的同步方式等待结果。
```
注入已有线程池,可以通过适配器接入其提交方法:
```go
type taskPool interface {
Submit(task func())
}
type poolDispatcher struct {
pool taskPool
}
func (d poolDispatcher) Schedule(task func()) {
d.pool.Submit(task)
}
func newCounter(pool taskPool) *actor.Actor[int] {
total := 0
return actor.New(func(value int) {
total += value
}, actor.WithDispatcher[int](poolDispatcher{pool: pool}))
}
```
`Dispatcher` 只有 `Schedule(task func())` 方法。它可以同步或异步执行,但必须接受任务并最终恰好执行一次,不得拒绝、静默丢弃或 panic;共享实例还必须支持并发调用。上述线程池的 `Submit` 同样需要满足这个约定。mailbox 在调用 `Schedule` 前已取得消费权,任务尚未执行时的新消息不会重复提交任务;库不为违反调度契约的实现提供回退。自定义 dispatcher 接口中包含 nil 指针时,调用方负责保证其 `Schedule` 仍有效。
FIFO 按消息成功发布的原子操作排序,固定区与动态区共享这个顺序。生产者先预留固定位置并不会占据 FIFO 顺序;它在发布前暂停时,其他生产者仍能发布并处理消息。消费者通过原子交换取出一批节点,反转链接后按 FIFO 处理,不复制消息来扩容。
处理函数不会并发或重入。处理函数向自身 Actor 发送的消息只会入队,必须等当前处理结束才能执行;不要同步等待它的执行结果。向使用默认 dispatcher 的其他空闲 Actor 发送消息会同步进入对方的处理函数,因此也应避免循环等待和过深的同步调用链。持续繁忙的 mailbox 可能让同步 `Send` 一直不返回;`Gosched` 只让出 CPU,不会切换调度策略,也不会打断单条处理函数。
`Send` 复制的是 Go 值,不深复制其中的指针、切片或 map;调用方需自行同步对共享数据的访问。固定槽位消费后清零,动态节点清除引用后交由 GC 回收。Actor 的可变状态应只在处理函数中访问,或自行同步。Actor 必须通过 `New` 创建,开始使用后不可复制。
内部职责分别放在 `actor.go`(门面与组装)、`queue.go`(存储与 FIFO)、`mailbox.go`(消费权、消费循环与调度)、`invoker.go`(处理函数与 panic 隔离)、`dispatcher.go`(执行策略契约)和 `options.go`(构造配置)。不包含生命周期、取消或自动重试。