主要优化
- err_group:去掉多余 goroutine,避免潜在泄漏;并把并发 append 改为按下标写
入,消除数据竞争。
- err_group 测试稳定性增强:放宽超时并增加结果长度断言。
- semaphore:修复等待队列元素类型断言错误(*waiter);补充非法参数校验(负数
acquire/release)。
- SemaChan:修复 Lock/Unlock 逻辑(初始化令牌桶),避免永久阻塞。
- observer:修复“每次 Notify 都启动新 fanout 协程”的问题:改为 sync.Once 只启动一次
fanOut。
- observer:修复并发读写观察者列表问题:给 Attach/Detach/fanOut 增加读写锁保护。
- observer:去掉 fanout 内部额外再起 goroutine和自动关闭所有 observer 的行为,避
免重复关闭/竞态风险(仍保留 Detach 时关闭单个 observer)。
- lock_free:修复可取消延迟队列的计数错误与 timers map 并发访问问题。
- lock_free:checkAckStatus 改为非阻塞读取,避免入队路径被卡住。
- routine:提供默认空任务并忽略 nil taskFn,防止空指针调用。
- ticker:发送改为非阻塞,Stop 幂等化,降低阻塞和重复关闭风险。
- query_builder:WaitAndGo 增加 goroutine 内 panic 转 error;测试里
的 GORM filter 链式写法修正。
新增测试
- 新增 semaphore 测试,覆盖 Acquire/Release/TryAcquire 与 SemaChan 并发上限。
162 lines
3.1 KiB
Go
162 lines
3.1 KiB
Go
package semaphore
|
|
|
|
import (
|
|
"container/list"
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
)
|
|
|
|
type waiter struct {
|
|
n int64
|
|
ready chan<- struct{} // 唤醒信号
|
|
}
|
|
|
|
type Semaphore struct {
|
|
size int64 // 资源数量
|
|
cur int64 // 当前已使用的资源数量
|
|
mu sync.Mutex
|
|
waiters list.List // 等待队列
|
|
}
|
|
|
|
func NewSemaphore(n int64) *Semaphore {
|
|
return &Semaphore{
|
|
size: n,
|
|
}
|
|
}
|
|
|
|
func (s *Semaphore) Acquire(ctx context.Context, n int64) error {
|
|
if n <= 0 {
|
|
if n == 0 {
|
|
return nil
|
|
}
|
|
return errors.New("semaphore: negative acquire")
|
|
}
|
|
|
|
done := ctx.Done()
|
|
|
|
s.mu.Lock()
|
|
// 保证 ctx.Done() happened before Semaphore.Acquire()
|
|
select {
|
|
case <-done:
|
|
s.mu.Unlock()
|
|
return ctx.Err()
|
|
default:
|
|
}
|
|
|
|
// 快速路径:如果当前可用资源足够,直接分配
|
|
if s.size-s.cur >= n && s.waiters.Len() == 0 {
|
|
s.cur += n
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// 慢路径:需要等待释放资源
|
|
return s.acquireSlow(ctx, n)
|
|
}
|
|
|
|
func (s *Semaphore) acquireSlow(ctx context.Context, n int64) error {
|
|
done := ctx.Done()
|
|
|
|
// 如果请求的资源数量超过了所能提供的资源数量,则只能依靠 ctx.Done() 来退出
|
|
if n > s.size {
|
|
s.mu.Unlock()
|
|
<-done
|
|
return ctx.Err()
|
|
}
|
|
|
|
// 资源不足,将调用者加入等待队列
|
|
// 同时创建一个信号通道,用于唤醒等待的调用者
|
|
ready := make(chan struct{})
|
|
w := &waiter{n: n, ready: ready}
|
|
elem := s.waiters.PushBack(w)
|
|
s.mu.Unlock()
|
|
|
|
select {
|
|
case <-done:
|
|
s.mu.Lock()
|
|
select {
|
|
case <-ready:
|
|
// 如果已经被唤醒,假装已经成功获取资源
|
|
s.cur -= n
|
|
s.notifyWaiters()
|
|
default:
|
|
// 如果还没有被唤醒,从等待队列中移除调用者自己
|
|
isFront := s.waiters.Front() == elem
|
|
s.waiters.Remove(elem)
|
|
// 如果当前调用者是队列的第一个且有多余资源,唤醒下一个等待者
|
|
if isFront && s.size > s.cur {
|
|
s.notifyWaiters()
|
|
}
|
|
}
|
|
s.mu.Unlock()
|
|
return ctx.Err()
|
|
|
|
case <-ready:
|
|
// 成功获取资源,唤醒信号已发送
|
|
select {
|
|
case <-done:
|
|
s.Release(n)
|
|
return ctx.Err()
|
|
default:
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (s *Semaphore) notifyWaiters() {
|
|
for {
|
|
next := s.waiters.Front()
|
|
if next == nil {
|
|
break // 没有等待者
|
|
}
|
|
|
|
w := next.Value.(*waiter)
|
|
if s.size-s.cur < w.n {
|
|
// 没有足够的资源满足下一个等待者
|
|
// 没有必要继续唤醒后续等待者,防止有等待者处于饥饿状态
|
|
break
|
|
}
|
|
|
|
s.cur += w.n
|
|
s.waiters.Remove(next)
|
|
close(w.ready) // 唤醒等待者
|
|
}
|
|
}
|
|
|
|
func (s *Semaphore) Release(n int64) {
|
|
if n <= 0 {
|
|
if n == 0 {
|
|
return
|
|
}
|
|
panic("semaphore: negative release")
|
|
}
|
|
|
|
s.mu.Lock()
|
|
s.cur -= n // 释放 n 个资源
|
|
|
|
if s.cur < 0 {
|
|
s.mu.Unlock()
|
|
panic("semaphore: released more than held")
|
|
}
|
|
|
|
s.notifyWaiters() // 唤醒等待者
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
func (s *Semaphore) TryAcquire(n int64) bool {
|
|
if n <= 0 {
|
|
return n == 0
|
|
}
|
|
|
|
s.mu.Lock()
|
|
// 检查当前可用资源是否足够,并且还没有等待者
|
|
success := s.size-s.cur >= n && s.waiters.Len() == 0
|
|
if success {
|
|
s.cur += n // 分配资源
|
|
}
|
|
|
|
s.mu.Unlock()
|
|
return success
|
|
}
|